diff --git a/.editorconfig b/.editorconfig index 60d4fd6cae..140cc085c5 100644 --- a/.editorconfig +++ b/.editorconfig @@ -1,901 +1,901 @@ -[*] -charset = utf-8 -end_of_line = crlf -indent_size = 4 -indent_style = space -insert_final_newline = true -max_line_length = 160 -tab_width = 4 -ij_continuation_indent_size = 8 -ij_formatter_off_tag = @formatter:off -ij_formatter_on_tag = @formatter:on -ij_formatter_tags_enabled = false -ij_smart_tabs = false -ij_visual_guides = none -ij_wrap_on_typing = false - -[*.java] -ij_java_align_consecutive_assignments = false -ij_java_align_consecutive_variable_declarations = false -ij_java_align_group_field_declarations = false -ij_java_align_multiline_annotation_parameters = false -ij_java_align_multiline_array_initializer_expression = false -ij_java_align_multiline_assignment = false -ij_java_align_multiline_binary_operation = false -ij_java_align_multiline_chained_methods = false -ij_java_align_multiline_extends_list = false -ij_java_align_multiline_for = true -ij_java_align_multiline_method_parentheses = false -ij_java_align_multiline_parameters = true -ij_java_align_multiline_parameters_in_calls = false -ij_java_align_multiline_parenthesized_expression = false -ij_java_align_multiline_records = true -ij_java_align_multiline_resources = true -ij_java_align_multiline_ternary_operation = false -ij_java_align_multiline_text_blocks = false -ij_java_align_multiline_throws_list = false -ij_java_align_subsequent_simple_methods = false -ij_java_align_throws_keyword = false -ij_java_annotation_parameter_wrap = off -ij_java_array_initializer_new_line_after_left_brace = false -ij_java_array_initializer_right_brace_on_new_line = false -ij_java_array_initializer_wrap = off -ij_java_assert_statement_colon_on_next_line = false -ij_java_assert_statement_wrap = off -ij_java_assignment_wrap = off -ij_java_binary_operation_sign_on_next_line = false -ij_java_binary_operation_wrap = off -ij_java_blank_lines_after_anonymous_class_header = 0 -ij_java_blank_lines_after_class_header = 0 -ij_java_blank_lines_after_imports = 1 -ij_java_blank_lines_after_package = 1 -ij_java_blank_lines_around_class = 1 -ij_java_blank_lines_around_field = 0 -ij_java_blank_lines_around_field_in_interface = 0 -ij_java_blank_lines_around_initializer = 1 -ij_java_blank_lines_around_method = 1 -ij_java_blank_lines_around_method_in_interface = 1 -ij_java_blank_lines_before_class_end = 0 -ij_java_blank_lines_before_imports = 1 -ij_java_blank_lines_before_method_body = 0 -ij_java_blank_lines_before_package = 0 -ij_java_block_brace_style = end_of_line -ij_java_block_comment_at_first_column = true -ij_java_builder_methods = none -ij_java_call_parameters_new_line_after_left_paren = false -ij_java_call_parameters_right_paren_on_new_line = false -ij_java_call_parameters_wrap = off -ij_java_case_statement_on_separate_line = true -ij_java_catch_on_new_line = false -ij_java_class_annotation_wrap = split_into_lines -ij_java_class_brace_style = end_of_line -ij_java_class_count_to_use_import_on_demand = 99 -ij_java_class_names_in_javadoc = 1 -ij_java_do_not_indent_top_level_class_members = false -ij_java_do_not_wrap_after_single_annotation = false -ij_java_do_while_brace_force = never -ij_java_doc_add_blank_line_after_description = true -ij_java_doc_add_blank_line_after_param_comments = false -ij_java_doc_add_blank_line_after_return = false -ij_java_doc_add_p_tag_on_empty_lines = true -ij_java_doc_align_exception_comments = true -ij_java_doc_align_param_comments = true -ij_java_doc_do_not_wrap_if_one_line = false -ij_java_doc_enable_formatting = true -ij_java_doc_enable_leading_asterisks = true -ij_java_doc_indent_on_continuation = false -ij_java_doc_keep_empty_lines = true -ij_java_doc_keep_empty_parameter_tag = true -ij_java_doc_keep_empty_return_tag = true -ij_java_doc_keep_empty_throws_tag = true -ij_java_doc_keep_invalid_tags = true -ij_java_doc_param_description_on_new_line = false -ij_java_doc_preserve_line_breaks = false -ij_java_doc_use_throws_not_exception_tag = true -ij_java_else_on_new_line = false -ij_java_enum_constants_wrap = off -ij_java_extends_keyword_wrap = off -ij_java_extends_list_wrap = off -ij_java_field_annotation_wrap = split_into_lines -ij_java_finally_on_new_line = false -ij_java_for_brace_force = never -ij_java_for_statement_new_line_after_left_paren = false -ij_java_for_statement_right_paren_on_new_line = false -ij_java_for_statement_wrap = off -ij_java_generate_final_locals = false -ij_java_generate_final_parameters = false -ij_java_if_brace_force = never -ij_java_imports_layout = $android.**,$androidx.**,$com.**,$junit.**,$net.**,$org.**,$java.**,$javax.**,$*,|,android.**,|,androidx.**,|,com.**,|,junit.**,|,net.**,|,org.**,|,java.**,|,javax.**,|,*,| -ij_java_indent_case_from_switch = true -ij_java_insert_inner_class_imports = false -ij_java_insert_override_annotation = true -ij_java_keep_blank_lines_before_right_brace = 2 -ij_java_keep_blank_lines_between_package_declaration_and_header = 2 -ij_java_keep_blank_lines_in_code = 2 -ij_java_keep_blank_lines_in_declarations = 2 -ij_java_keep_builder_methods_indents = false -ij_java_keep_control_statement_in_one_line = true -ij_java_keep_first_column_comment = true -ij_java_keep_indents_on_empty_lines = false -ij_java_keep_line_breaks = true -ij_java_keep_multiple_expressions_in_one_line = false -ij_java_keep_simple_blocks_in_one_line = false -ij_java_keep_simple_classes_in_one_line = false -ij_java_keep_simple_lambdas_in_one_line = false -ij_java_keep_simple_methods_in_one_line = false -ij_java_label_indent_absolute = false -ij_java_label_indent_size = 0 -ij_java_lambda_brace_style = end_of_line -ij_java_layout_static_imports_separately = true -ij_java_line_comment_add_space = false -ij_java_line_comment_at_first_column = true -ij_java_method_annotation_wrap = split_into_lines -ij_java_method_brace_style = end_of_line -ij_java_method_call_chain_wrap = off -ij_java_method_parameters_new_line_after_left_paren = false -ij_java_method_parameters_right_paren_on_new_line = false -ij_java_method_parameters_wrap = off -ij_java_modifier_list_wrap = false -ij_java_names_count_to_use_import_on_demand = 99 -ij_java_new_line_after_lparen_in_record_header = false -ij_java_parameter_annotation_wrap = off -ij_java_parentheses_expression_new_line_after_left_paren = false -ij_java_parentheses_expression_right_paren_on_new_line = false -ij_java_place_assignment_sign_on_next_line = false -ij_java_prefer_longer_names = true -ij_java_prefer_parameters_wrap = false -ij_java_record_components_wrap = normal -ij_java_repeat_synchronized = true -ij_java_replace_instanceof_and_cast = false -ij_java_replace_null_check = true -ij_java_replace_sum_lambda_with_method_ref = true -ij_java_resource_list_new_line_after_left_paren = false -ij_java_resource_list_right_paren_on_new_line = false -ij_java_resource_list_wrap = off -ij_java_rparen_on_new_line_in_record_header = false -ij_java_space_after_closing_angle_bracket_in_type_argument = false -ij_java_space_after_colon = true -ij_java_space_after_comma = true -ij_java_space_after_comma_in_type_arguments = true -ij_java_space_after_for_semicolon = true -ij_java_space_after_quest = true -ij_java_space_after_type_cast = true -ij_java_space_before_annotation_array_initializer_left_brace = false -ij_java_space_before_annotation_parameter_list = false -ij_java_space_before_array_initializer_left_brace = false -ij_java_space_before_catch_keyword = true -ij_java_space_before_catch_left_brace = true -ij_java_space_before_catch_parentheses = true -ij_java_space_before_class_left_brace = true -ij_java_space_before_colon = true -ij_java_space_before_colon_in_foreach = true -ij_java_space_before_comma = false -ij_java_space_before_do_left_brace = true -ij_java_space_before_else_keyword = true -ij_java_space_before_else_left_brace = true -ij_java_space_before_finally_keyword = true -ij_java_space_before_finally_left_brace = true -ij_java_space_before_for_left_brace = true -ij_java_space_before_for_parentheses = true -ij_java_space_before_for_semicolon = false -ij_java_space_before_if_left_brace = true -ij_java_space_before_if_parentheses = true -ij_java_space_before_method_call_parentheses = false -ij_java_space_before_method_left_brace = true -ij_java_space_before_method_parentheses = false -ij_java_space_before_opening_angle_bracket_in_type_parameter = false -ij_java_space_before_quest = true -ij_java_space_before_switch_left_brace = true -ij_java_space_before_switch_parentheses = true -ij_java_space_before_synchronized_left_brace = true -ij_java_space_before_synchronized_parentheses = true -ij_java_space_before_try_left_brace = true -ij_java_space_before_try_parentheses = true -ij_java_space_before_type_parameter_list = false -ij_java_space_before_while_keyword = true -ij_java_space_before_while_left_brace = true -ij_java_space_before_while_parentheses = true -ij_java_space_inside_one_line_enum_braces = false -ij_java_space_within_empty_array_initializer_braces = false -ij_java_space_within_empty_method_call_parentheses = false -ij_java_space_within_empty_method_parentheses = false -ij_java_spaces_around_additive_operators = true -ij_java_spaces_around_assignment_operators = true -ij_java_spaces_around_bitwise_operators = true -ij_java_spaces_around_equality_operators = true -ij_java_spaces_around_lambda_arrow = true -ij_java_spaces_around_logical_operators = true -ij_java_spaces_around_method_ref_dbl_colon = false -ij_java_spaces_around_multiplicative_operators = true -ij_java_spaces_around_relational_operators = true -ij_java_spaces_around_shift_operators = true -ij_java_spaces_around_type_bounds_in_type_parameters = true -ij_java_spaces_around_unary_operator = false -ij_java_spaces_within_angle_brackets = false -ij_java_spaces_within_annotation_parentheses = false -ij_java_spaces_within_array_initializer_braces = false -ij_java_spaces_within_braces = false -ij_java_spaces_within_brackets = false -ij_java_spaces_within_cast_parentheses = false -ij_java_spaces_within_catch_parentheses = false -ij_java_spaces_within_for_parentheses = false -ij_java_spaces_within_if_parentheses = false -ij_java_spaces_within_method_call_parentheses = false -ij_java_spaces_within_method_parentheses = false -ij_java_spaces_within_parentheses = false -ij_java_spaces_within_record_header = false -ij_java_spaces_within_switch_parentheses = false -ij_java_spaces_within_synchronized_parentheses = false -ij_java_spaces_within_try_parentheses = false -ij_java_spaces_within_while_parentheses = false -ij_java_special_else_if_treatment = true -ij_java_subclass_name_suffix = Impl -ij_java_ternary_operation_signs_on_next_line = false -ij_java_ternary_operation_wrap = off -ij_java_test_name_suffix = Test -ij_java_throws_keyword_wrap = off -ij_java_throws_list_wrap = off -ij_java_use_external_annotations = false -ij_java_use_fq_class_names = false -ij_java_use_relative_indents = false -ij_java_use_single_class_imports = true -ij_java_variable_annotation_wrap = off -ij_java_visibility = public -ij_java_while_brace_force = never -ij_java_while_on_new_line = false -ij_java_wrap_comments = false -ij_java_wrap_first_method_in_call_chain = false -ij_java_wrap_long_lines = false - -[*.properties] -ij_properties_align_group_field_declarations = false -ij_properties_keep_blank_lines = false -ij_properties_key_value_delimiter = equals -ij_properties_spaces_around_key_value_delimiter = false - -[.editorconfig] -ij_editorconfig_align_group_field_declarations = false -ij_editorconfig_space_after_colon = false -ij_editorconfig_space_after_comma = true -ij_editorconfig_space_before_colon = false -ij_editorconfig_space_before_comma = false -ij_editorconfig_spaces_around_assignment_operators = true - -[{*.ant,*.fxml,*.jhm,*.jnlp,*.jrxml,*.rng,*.tld,*.wsdl,*.xml,*.xsd,*.xsl,*.xslt,*.xul}] -ij_continuation_indent_size = 4 -ij_xml_align_attributes = false -ij_xml_align_text = false -ij_xml_attribute_wrap = normal -ij_xml_block_comment_at_first_column = true -ij_xml_keep_blank_lines = 2 -ij_xml_keep_indents_on_empty_lines = false -ij_xml_keep_line_breaks = false -ij_xml_keep_line_breaks_in_text = true -ij_xml_keep_whitespaces = false -ij_xml_keep_whitespaces_around_cdata = preserve -ij_xml_keep_whitespaces_inside_cdata = false -ij_xml_line_comment_at_first_column = true -ij_xml_space_after_tag_name = false -ij_xml_space_around_equals_in_attribute = false -ij_xml_space_inside_empty_tag = true -ij_xml_text_wrap = normal -ij_xml_use_custom_settings = true - -[{*.bash,*.sh,*.zsh}] -indent_size = 2 -tab_width = 2 -ij_shell_binary_ops_start_line = false -ij_shell_keep_column_alignment_padding = false -ij_shell_minify_program = false -ij_shell_redirect_followed_by_space = false -ij_shell_switch_cases_indented = false -ij_shell_use_unix_line_separator = true - -[{*.c,*.c++,*.cc,*.cp,*.cpp,*.cu,*.cuh,*.cxx,*.h,*.h++,*.hh,*.hp,*.hpp,*.hxx,*.i,*.icc,*.ii,*.inl,*.ino,*.ipp,*.m,*.mm,*.pch,*.tcc,*.tpp}] -ij_c_add_brief_tag = false -ij_c_add_getter_prefix = true -ij_c_add_setter_prefix = true -ij_c_align_dictionary_pair_values = false -ij_c_align_group_field_declarations = false -ij_c_align_init_list_in_columns = true -ij_c_align_multiline_array_initializer_expression = true -ij_c_align_multiline_assignment = true -ij_c_align_multiline_binary_operation = true -ij_c_align_multiline_chained_methods = false -ij_c_align_multiline_for = true -ij_c_align_multiline_ternary_operation = true -ij_c_array_initializer_comma_on_next_line = false -ij_c_array_initializer_new_line_after_left_brace = false -ij_c_array_initializer_right_brace_on_new_line = false -ij_c_array_initializer_wrap = normal -ij_c_assignment_wrap = off -ij_c_binary_operation_sign_on_next_line = false -ij_c_binary_operation_wrap = normal -ij_c_blank_lines_after_class_header = 0 -ij_c_blank_lines_after_imports = 1 -ij_c_blank_lines_around_class = 1 -ij_c_blank_lines_around_field = 0 -ij_c_blank_lines_around_field_in_interface = 0 -ij_c_blank_lines_around_method = 1 -ij_c_blank_lines_around_method_in_interface = 1 -ij_c_blank_lines_around_namespace = 0 -ij_c_blank_lines_around_properties_in_declaration = 0 -ij_c_blank_lines_around_properties_in_interface = 0 -ij_c_blank_lines_before_imports = 1 -ij_c_blank_lines_before_method_body = 0 -ij_c_block_brace_placement = end_of_line -ij_c_block_brace_style = end_of_line -ij_c_block_comment_at_first_column = true -ij_c_catch_on_new_line = false -ij_c_class_brace_style = end_of_line -ij_c_class_constructor_init_list_align_multiline = true -ij_c_class_constructor_init_list_comma_on_next_line = false -ij_c_class_constructor_init_list_new_line_after_colon = never -ij_c_class_constructor_init_list_new_line_before_colon = if_long -ij_c_class_constructor_init_list_wrap = normal -ij_c_copy_is_deep = false -ij_c_create_interface_for_categories = true -ij_c_declare_generated_methods = true -ij_c_description_include_member_names = true -ij_c_discharged_short_ternary_operator = false -ij_c_do_not_add_breaks = false -ij_c_do_while_brace_force = never -ij_c_else_on_new_line = false -ij_c_enum_constants_comma_on_next_line = false -ij_c_enum_constants_wrap = on_every_item -ij_c_for_brace_force = never -ij_c_for_statement_new_line_after_left_paren = false -ij_c_for_statement_right_paren_on_new_line = false -ij_c_for_statement_wrap = off -ij_c_function_brace_placement = end_of_line -ij_c_function_call_arguments_align_multiline = true -ij_c_function_call_arguments_align_multiline_pars = false -ij_c_function_call_arguments_comma_on_next_line = false -ij_c_function_call_arguments_new_line_after_lpar = false -ij_c_function_call_arguments_new_line_before_rpar = false -ij_c_function_call_arguments_wrap = normal -ij_c_function_non_top_after_return_type_wrap = normal -ij_c_function_parameters_align_multiline = true -ij_c_function_parameters_align_multiline_pars = false -ij_c_function_parameters_comma_on_next_line = false -ij_c_function_parameters_new_line_after_lpar = false -ij_c_function_parameters_new_line_before_rpar = false -ij_c_function_parameters_wrap = normal -ij_c_function_top_after_return_type_wrap = normal -ij_c_generate_additional_eq_operators = true -ij_c_generate_additional_rel_operators = true -ij_c_generate_class_constructor = true -ij_c_generate_comparison_operators_use_std_tie = false -ij_c_generate_instance_variables_for_properties = ask -ij_c_generate_operators_as_members = true -ij_c_header_guard_style_pattern = ${PROJECT_NAME}_${FILE_NAME}_${EXT} -ij_c_if_brace_force = never -ij_c_in_line_short_ternary_operator = true -ij_c_indent_block_comment = true -ij_c_indent_c_struct_members = 4 -ij_c_indent_case_from_switch = true -ij_c_indent_class_members = 4 -ij_c_indent_directive_as_code = false -ij_c_indent_implementation_members = 0 -ij_c_indent_inside_code_block = 4 -ij_c_indent_interface_members = 0 -ij_c_indent_interface_members_except_ivars_block = false -ij_c_indent_namespace_members = 4 -ij_c_indent_preprocessor_directive = 0 -ij_c_indent_visibility_keywords = 0 -ij_c_insert_override = true -ij_c_insert_virtual_with_override = false -ij_c_introduce_auto_vars = false -ij_c_introduce_const_params = false -ij_c_introduce_const_vars = false -ij_c_introduce_generate_property = false -ij_c_introduce_generate_synthesize = true -ij_c_introduce_globals_to_header = true -ij_c_introduce_prop_to_private_category = false -ij_c_introduce_static_consts = true -ij_c_introduce_use_ns_types = false -ij_c_ivars_prefix = _ -ij_c_keep_blank_lines_before_end = 2 -ij_c_keep_blank_lines_before_right_brace = 2 -ij_c_keep_blank_lines_in_code = 2 -ij_c_keep_blank_lines_in_declarations = 2 -ij_c_keep_case_expressions_in_one_line = false -ij_c_keep_control_statement_in_one_line = true -ij_c_keep_directive_at_first_column = true -ij_c_keep_first_column_comment = true -ij_c_keep_line_breaks = true -ij_c_keep_nested_namespaces_in_one_line = false -ij_c_keep_simple_blocks_in_one_line = true -ij_c_keep_simple_methods_in_one_line = true -ij_c_keep_structures_in_one_line = false -ij_c_lambda_capture_list_align_multiline = false -ij_c_lambda_capture_list_align_multiline_bracket = false -ij_c_lambda_capture_list_comma_on_next_line = false -ij_c_lambda_capture_list_new_line_after_lbracket = false -ij_c_lambda_capture_list_new_line_before_rbracket = false -ij_c_lambda_capture_list_wrap = off -ij_c_line_comment_add_space = false -ij_c_line_comment_at_first_column = true -ij_c_method_brace_placement = end_of_line -ij_c_method_call_arguments_align_by_colons = true -ij_c_method_call_arguments_align_multiline = false -ij_c_method_call_arguments_special_dictionary_pairs_treatment = true -ij_c_method_call_arguments_wrap = off -ij_c_method_call_chain_wrap = off -ij_c_method_parameters_align_by_colons = true -ij_c_method_parameters_align_multiline = false -ij_c_method_parameters_wrap = off -ij_c_namespace_brace_placement = end_of_line -ij_c_parentheses_expression_new_line_after_left_paren = false -ij_c_parentheses_expression_right_paren_on_new_line = false -ij_c_place_assignment_sign_on_next_line = false -ij_c_property_nonatomic = true -ij_c_put_ivars_to_implementation = true -ij_c_refactor_compatibility_aliases_and_classes = true -ij_c_refactor_properties_and_ivars = true -ij_c_release_style = ivar -ij_c_retain_object_parameters_in_constructor = true -ij_c_semicolon_after_method_signature = false -ij_c_shift_operation_align_multiline = true -ij_c_shift_operation_wrap = normal -ij_c_show_non_virtual_functions = false -ij_c_space_after_colon = true -ij_c_space_after_colon_in_selector = false -ij_c_space_after_comma = true -ij_c_space_after_cup_in_blocks = false -ij_c_space_after_dictionary_literal_colon = true -ij_c_space_after_for_semicolon = true -ij_c_space_after_init_list_colon = true -ij_c_space_after_method_parameter_type_parentheses = false -ij_c_space_after_method_return_type_parentheses = false -ij_c_space_after_pointer_in_declaration = false -ij_c_space_after_quest = true -ij_c_space_after_reference_in_declaration = false -ij_c_space_after_reference_in_rvalue = false -ij_c_space_after_structures_rbrace = true -ij_c_space_after_superclass_colon = true -ij_c_space_after_type_cast = true -ij_c_space_after_visibility_sign_in_method_declaration = true -ij_c_space_before_autorelease_pool_lbrace = true -ij_c_space_before_catch_keyword = true -ij_c_space_before_catch_left_brace = true -ij_c_space_before_catch_parentheses = true -ij_c_space_before_category_parentheses = true -ij_c_space_before_chained_send_message = true -ij_c_space_before_class_left_brace = true -ij_c_space_before_colon = true -ij_c_space_before_comma = false -ij_c_space_before_dictionary_literal_colon = false -ij_c_space_before_do_left_brace = true -ij_c_space_before_else_keyword = true -ij_c_space_before_else_left_brace = true -ij_c_space_before_for_left_brace = true -ij_c_space_before_for_parentheses = true -ij_c_space_before_for_semicolon = false -ij_c_space_before_if_left_brace = true -ij_c_space_before_if_parentheses = true -ij_c_space_before_init_list = false -ij_c_space_before_init_list_colon = true -ij_c_space_before_method_call_parentheses = false -ij_c_space_before_method_left_brace = true -ij_c_space_before_method_parentheses = false -ij_c_space_before_namespace_lbrace = true -ij_c_space_before_pointer_in_declaration = true -ij_c_space_before_property_attributes_parentheses = false -ij_c_space_before_protocols_brackets = true -ij_c_space_before_quest = true -ij_c_space_before_reference_in_declaration = true -ij_c_space_before_superclass_colon = true -ij_c_space_before_switch_left_brace = true -ij_c_space_before_switch_parentheses = true -ij_c_space_before_template_call_lt = false -ij_c_space_before_template_declaration_lt = false -ij_c_space_before_try_left_brace = true -ij_c_space_before_while_keyword = true -ij_c_space_before_while_left_brace = true -ij_c_space_before_while_parentheses = true -ij_c_space_between_adjacent_brackets = false -ij_c_space_between_operator_and_punctuator = false -ij_c_space_within_empty_array_initializer_braces = false -ij_c_spaces_around_additive_operators = true -ij_c_spaces_around_assignment_operators = true -ij_c_spaces_around_bitwise_operators = true -ij_c_spaces_around_equality_operators = true -ij_c_spaces_around_lambda_arrow = true -ij_c_spaces_around_logical_operators = true -ij_c_spaces_around_multiplicative_operators = true -ij_c_spaces_around_pm_operators = false -ij_c_spaces_around_relational_operators = true -ij_c_spaces_around_shift_operators = true -ij_c_spaces_around_unary_operator = false -ij_c_spaces_within_array_initializer_braces = false -ij_c_spaces_within_braces = true -ij_c_spaces_within_brackets = false -ij_c_spaces_within_cast_parentheses = false -ij_c_spaces_within_catch_parentheses = false -ij_c_spaces_within_category_parentheses = false -ij_c_spaces_within_empty_braces = false -ij_c_spaces_within_empty_function_call_parentheses = false -ij_c_spaces_within_empty_function_declaration_parentheses = false -ij_c_spaces_within_empty_lambda_capture_list_bracket = false -ij_c_spaces_within_empty_template_call_ltgt = false -ij_c_spaces_within_empty_template_declaration_ltgt = false -ij_c_spaces_within_for_parentheses = false -ij_c_spaces_within_function_call_parentheses = false -ij_c_spaces_within_function_declaration_parentheses = false -ij_c_spaces_within_if_parentheses = false -ij_c_spaces_within_lambda_capture_list_bracket = false -ij_c_spaces_within_method_parameter_type_parentheses = false -ij_c_spaces_within_method_return_type_parentheses = false -ij_c_spaces_within_parentheses = false -ij_c_spaces_within_property_attributes_parentheses = false -ij_c_spaces_within_protocols_brackets = false -ij_c_spaces_within_send_message_brackets = false -ij_c_spaces_within_switch_parentheses = false -ij_c_spaces_within_template_call_ltgt = false -ij_c_spaces_within_template_declaration_ltgt = false -ij_c_spaces_within_template_double_gt = true -ij_c_spaces_within_while_parentheses = false -ij_c_special_else_if_treatment = true -ij_c_superclass_list_after_colon = never -ij_c_superclass_list_align_multiline = true -ij_c_superclass_list_before_colon = if_long -ij_c_superclass_list_comma_on_next_line = false -ij_c_superclass_list_wrap = on_every_item -ij_c_tag_prefix_of_block_comment = at -ij_c_tag_prefix_of_line_comment = back_slash -ij_c_template_call_arguments_align_multiline = false -ij_c_template_call_arguments_align_multiline_pars = false -ij_c_template_call_arguments_comma_on_next_line = false -ij_c_template_call_arguments_new_line_after_lt = false -ij_c_template_call_arguments_new_line_before_gt = false -ij_c_template_call_arguments_wrap = off -ij_c_template_declaration_function_body_indent = false -ij_c_template_declaration_function_wrap = split_into_lines -ij_c_template_declaration_struct_body_indent = false -ij_c_template_declaration_struct_wrap = split_into_lines -ij_c_template_parameters_align_multiline = false -ij_c_template_parameters_align_multiline_pars = false -ij_c_template_parameters_comma_on_next_line = false -ij_c_template_parameters_new_line_after_lt = false -ij_c_template_parameters_new_line_before_gt = false -ij_c_template_parameters_wrap = off -ij_c_ternary_operation_signs_on_next_line = true -ij_c_ternary_operation_wrap = normal -ij_c_type_qualifiers_placement = before -ij_c_use_modern_casts = true -ij_c_use_setters_in_constructor = true -ij_c_while_brace_force = never -ij_c_while_on_new_line = false -ij_c_wrap_property_declaration = off - -[{*.cmake,CMakeLists.txt}] -ij_cmake_align_multiline_parameters_in_calls = false -ij_cmake_force_commands_case = 2 -ij_cmake_keep_blank_lines_in_code = 2 -ij_cmake_space_before_for_parentheses = true -ij_cmake_space_before_if_parentheses = true -ij_cmake_space_before_method_call_parentheses = false -ij_cmake_space_before_method_parentheses = false -ij_cmake_space_before_while_parentheses = true -ij_cmake_spaces_within_for_parentheses = false -ij_cmake_spaces_within_if_parentheses = false -ij_cmake_spaces_within_method_call_parentheses = false -ij_cmake_spaces_within_method_parentheses = false -ij_cmake_spaces_within_while_parentheses = false - -[{*.gant,*.gradle,*.groovy,*.gy}] -ij_groovy_align_group_field_declarations = false -ij_groovy_align_multiline_array_initializer_expression = false -ij_groovy_align_multiline_assignment = false -ij_groovy_align_multiline_binary_operation = false -ij_groovy_align_multiline_chained_methods = false -ij_groovy_align_multiline_extends_list = false -ij_groovy_align_multiline_for = true -ij_groovy_align_multiline_list_or_map = true -ij_groovy_align_multiline_method_parentheses = false -ij_groovy_align_multiline_parameters = true -ij_groovy_align_multiline_parameters_in_calls = false -ij_groovy_align_multiline_resources = true -ij_groovy_align_multiline_ternary_operation = false -ij_groovy_align_multiline_throws_list = false -ij_groovy_align_named_args_in_map = true -ij_groovy_align_throws_keyword = false -ij_groovy_array_initializer_new_line_after_left_brace = false -ij_groovy_array_initializer_right_brace_on_new_line = false -ij_groovy_array_initializer_wrap = off -ij_groovy_assert_statement_wrap = off -ij_groovy_assignment_wrap = off -ij_groovy_binary_operation_wrap = off -ij_groovy_blank_lines_after_class_header = 0 -ij_groovy_blank_lines_after_imports = 1 -ij_groovy_blank_lines_after_package = 1 -ij_groovy_blank_lines_around_class = 1 -ij_groovy_blank_lines_around_field = 0 -ij_groovy_blank_lines_around_field_in_interface = 0 -ij_groovy_blank_lines_around_method = 1 -ij_groovy_blank_lines_around_method_in_interface = 1 -ij_groovy_blank_lines_before_imports = 1 -ij_groovy_blank_lines_before_method_body = 0 -ij_groovy_blank_lines_before_package = 0 -ij_groovy_block_brace_style = end_of_line -ij_groovy_block_comment_at_first_column = true -ij_groovy_call_parameters_new_line_after_left_paren = false -ij_groovy_call_parameters_right_paren_on_new_line = false -ij_groovy_call_parameters_wrap = off -ij_groovy_catch_on_new_line = false -ij_groovy_class_annotation_wrap = split_into_lines -ij_groovy_class_brace_style = end_of_line -ij_groovy_class_count_to_use_import_on_demand = 5 -ij_groovy_do_while_brace_force = never -ij_groovy_else_on_new_line = false -ij_groovy_enum_constants_wrap = off -ij_groovy_extends_keyword_wrap = off -ij_groovy_extends_list_wrap = off -ij_groovy_field_annotation_wrap = split_into_lines -ij_groovy_finally_on_new_line = false -ij_groovy_for_brace_force = never -ij_groovy_for_statement_new_line_after_left_paren = false -ij_groovy_for_statement_right_paren_on_new_line = false -ij_groovy_for_statement_wrap = off -ij_groovy_if_brace_force = never -ij_groovy_import_annotation_wrap = 2 -ij_groovy_imports_layout = *,|,javax.**,java.**,|,$* -ij_groovy_indent_case_from_switch = true -ij_groovy_indent_label_blocks = true -ij_groovy_insert_inner_class_imports = false -ij_groovy_keep_blank_lines_before_right_brace = 2 -ij_groovy_keep_blank_lines_in_code = 2 -ij_groovy_keep_blank_lines_in_declarations = 2 -ij_groovy_keep_control_statement_in_one_line = true -ij_groovy_keep_first_column_comment = true -ij_groovy_keep_indents_on_empty_lines = false -ij_groovy_keep_line_breaks = true -ij_groovy_keep_multiple_expressions_in_one_line = false -ij_groovy_keep_simple_blocks_in_one_line = false -ij_groovy_keep_simple_classes_in_one_line = true -ij_groovy_keep_simple_lambdas_in_one_line = true -ij_groovy_keep_simple_methods_in_one_line = true -ij_groovy_label_indent_absolute = false -ij_groovy_label_indent_size = 0 -ij_groovy_lambda_brace_style = end_of_line -ij_groovy_layout_static_imports_separately = true -ij_groovy_line_comment_add_space = false -ij_groovy_line_comment_at_first_column = true -ij_groovy_method_annotation_wrap = split_into_lines -ij_groovy_method_brace_style = end_of_line -ij_groovy_method_call_chain_wrap = off -ij_groovy_method_parameters_new_line_after_left_paren = false -ij_groovy_method_parameters_right_paren_on_new_line = false -ij_groovy_method_parameters_wrap = off -ij_groovy_modifier_list_wrap = false -ij_groovy_names_count_to_use_import_on_demand = 3 -ij_groovy_parameter_annotation_wrap = off -ij_groovy_parentheses_expression_new_line_after_left_paren = false -ij_groovy_parentheses_expression_right_paren_on_new_line = false -ij_groovy_prefer_parameters_wrap = false -ij_groovy_resource_list_new_line_after_left_paren = false -ij_groovy_resource_list_right_paren_on_new_line = false -ij_groovy_resource_list_wrap = off -ij_groovy_space_after_assert_separator = true -ij_groovy_space_after_colon = true -ij_groovy_space_after_comma = true -ij_groovy_space_after_comma_in_type_arguments = true -ij_groovy_space_after_for_semicolon = true -ij_groovy_space_after_quest = true -ij_groovy_space_after_type_cast = true -ij_groovy_space_before_annotation_parameter_list = false -ij_groovy_space_before_array_initializer_left_brace = false -ij_groovy_space_before_assert_separator = false -ij_groovy_space_before_catch_keyword = true -ij_groovy_space_before_catch_left_brace = true -ij_groovy_space_before_catch_parentheses = true -ij_groovy_space_before_class_left_brace = true -ij_groovy_space_before_closure_left_brace = true -ij_groovy_space_before_colon = true -ij_groovy_space_before_comma = false -ij_groovy_space_before_do_left_brace = true -ij_groovy_space_before_else_keyword = true -ij_groovy_space_before_else_left_brace = true -ij_groovy_space_before_finally_keyword = true -ij_groovy_space_before_finally_left_brace = true -ij_groovy_space_before_for_left_brace = true -ij_groovy_space_before_for_parentheses = true -ij_groovy_space_before_for_semicolon = false -ij_groovy_space_before_if_left_brace = true -ij_groovy_space_before_if_parentheses = true -ij_groovy_space_before_method_call_parentheses = false -ij_groovy_space_before_method_left_brace = true -ij_groovy_space_before_method_parentheses = false -ij_groovy_space_before_quest = true -ij_groovy_space_before_switch_left_brace = true -ij_groovy_space_before_switch_parentheses = true -ij_groovy_space_before_synchronized_left_brace = true -ij_groovy_space_before_synchronized_parentheses = true -ij_groovy_space_before_try_left_brace = true -ij_groovy_space_before_try_parentheses = true -ij_groovy_space_before_while_keyword = true -ij_groovy_space_before_while_left_brace = true -ij_groovy_space_before_while_parentheses = true -ij_groovy_space_in_named_argument = true -ij_groovy_space_in_named_argument_before_colon = false -ij_groovy_space_within_empty_array_initializer_braces = false -ij_groovy_space_within_empty_method_call_parentheses = false -ij_groovy_spaces_around_additive_operators = true -ij_groovy_spaces_around_assignment_operators = true -ij_groovy_spaces_around_bitwise_operators = true -ij_groovy_spaces_around_equality_operators = true -ij_groovy_spaces_around_lambda_arrow = true -ij_groovy_spaces_around_logical_operators = true -ij_groovy_spaces_around_multiplicative_operators = true -ij_groovy_spaces_around_regex_operators = true -ij_groovy_spaces_around_relational_operators = true -ij_groovy_spaces_around_shift_operators = true -ij_groovy_spaces_within_annotation_parentheses = false -ij_groovy_spaces_within_array_initializer_braces = false -ij_groovy_spaces_within_braces = true -ij_groovy_spaces_within_brackets = false -ij_groovy_spaces_within_cast_parentheses = false -ij_groovy_spaces_within_catch_parentheses = false -ij_groovy_spaces_within_for_parentheses = false -ij_groovy_spaces_within_gstring_injection_braces = false -ij_groovy_spaces_within_if_parentheses = false -ij_groovy_spaces_within_list_or_map = false -ij_groovy_spaces_within_method_call_parentheses = false -ij_groovy_spaces_within_method_parentheses = false -ij_groovy_spaces_within_parentheses = false -ij_groovy_spaces_within_switch_parentheses = false -ij_groovy_spaces_within_synchronized_parentheses = false -ij_groovy_spaces_within_try_parentheses = false -ij_groovy_spaces_within_tuple_expression = false -ij_groovy_spaces_within_while_parentheses = false -ij_groovy_special_else_if_treatment = true -ij_groovy_ternary_operation_wrap = off -ij_groovy_throws_keyword_wrap = off -ij_groovy_throws_list_wrap = off -ij_groovy_use_flying_geese_braces = false -ij_groovy_use_fq_class_names = false -ij_groovy_use_fq_class_names_in_javadoc = true -ij_groovy_use_relative_indents = false -ij_groovy_use_single_class_imports = true -ij_groovy_variable_annotation_wrap = off -ij_groovy_while_brace_force = never -ij_groovy_while_on_new_line = false -ij_groovy_wrap_long_lines = false - -[{*.gradle.kts,*.kt,*.kts,*.main.kts}] -ij_kotlin_align_in_columns_case_branch = true -ij_kotlin_align_multiline_binary_operation = false -ij_kotlin_align_multiline_extends_list = false -ij_kotlin_align_multiline_method_parentheses = false -ij_kotlin_align_multiline_parameters = true -ij_kotlin_align_multiline_parameters_in_calls = false -ij_kotlin_allow_trailing_comma = false -ij_kotlin_allow_trailing_comma_on_call_site = false -ij_kotlin_assignment_wrap = off -ij_kotlin_blank_lines_after_class_header = 0 -ij_kotlin_blank_lines_around_block_when_branches = 0 -ij_kotlin_blank_lines_before_declaration_with_comment_or_annotation_on_separate_line = 1 -ij_kotlin_block_comment_at_first_column = true -ij_kotlin_call_parameters_new_line_after_left_paren = false -ij_kotlin_call_parameters_right_paren_on_new_line = false -ij_kotlin_call_parameters_wrap = off -ij_kotlin_catch_on_new_line = false -ij_kotlin_class_annotation_wrap = off -ij_kotlin_code_style_defaults = KOTLIN_OFFICIAL -ij_kotlin_continuation_indent_for_chained_calls = true -ij_kotlin_continuation_indent_for_expression_bodies = true -ij_kotlin_continuation_indent_in_argument_lists = true -ij_kotlin_continuation_indent_in_elvis = true -ij_kotlin_continuation_indent_in_if_conditions = true -ij_kotlin_continuation_indent_in_parameter_lists = true -ij_kotlin_continuation_indent_in_supertype_lists = true -ij_kotlin_else_on_new_line = false -ij_kotlin_enum_constants_wrap = off -ij_kotlin_extends_list_wrap = off -ij_kotlin_field_annotation_wrap = normal -ij_kotlin_finally_on_new_line = false -ij_kotlin_if_rparen_on_new_line = false -ij_kotlin_import_nested_classes = false -ij_kotlin_imports_layout = *,java.**,javax.**,kotlin.**,^ -ij_kotlin_insert_whitespaces_in_simple_one_line_method = true -ij_kotlin_keep_blank_lines_before_right_brace = 0 -ij_kotlin_keep_blank_lines_in_code = 1 -ij_kotlin_keep_blank_lines_in_declarations = 1 -ij_kotlin_keep_first_column_comment = true -ij_kotlin_keep_indents_on_empty_lines = false -ij_kotlin_keep_line_breaks = true -ij_kotlin_lbrace_on_next_line = false -ij_kotlin_line_comment_add_space = false -ij_kotlin_line_comment_at_first_column = true -ij_kotlin_method_annotation_wrap = split_into_lines -ij_kotlin_method_call_chain_wrap = off -ij_kotlin_method_parameters_new_line_after_left_paren = false -ij_kotlin_method_parameters_right_paren_on_new_line = false -ij_kotlin_method_parameters_wrap = off -ij_kotlin_name_count_to_use_star_import = 2147483647 -ij_kotlin_name_count_to_use_star_import_for_members = 2147483647 -ij_kotlin_packages_to_use_import_on_demand = kotlinx.android.synthetic.** -ij_kotlin_parameter_annotation_wrap = off -ij_kotlin_space_after_comma = true -ij_kotlin_space_after_extend_colon = true -ij_kotlin_space_after_type_colon = true -ij_kotlin_space_before_catch_parentheses = true -ij_kotlin_space_before_comma = false -ij_kotlin_space_before_extend_colon = true -ij_kotlin_space_before_for_parentheses = true -ij_kotlin_space_before_if_parentheses = true -ij_kotlin_space_before_lambda_arrow = true -ij_kotlin_space_before_type_colon = false -ij_kotlin_space_before_when_parentheses = true -ij_kotlin_space_before_while_parentheses = true -ij_kotlin_spaces_around_additive_operators = true -ij_kotlin_spaces_around_assignment_operators = true -ij_kotlin_spaces_around_equality_operators = true -ij_kotlin_spaces_around_function_type_arrow = true -ij_kotlin_spaces_around_logical_operators = true -ij_kotlin_spaces_around_multiplicative_operators = true -ij_kotlin_spaces_around_range = false -ij_kotlin_spaces_around_relational_operators = true -ij_kotlin_spaces_around_unary_operator = false -ij_kotlin_spaces_around_when_arrow = true -ij_kotlin_use_custom_formatting_for_modifiers = true -ij_kotlin_variable_annotation_wrap = off -ij_kotlin_while_on_new_line = false -ij_kotlin_wrap_elvis_expressions = 1 -ij_kotlin_wrap_expression_body_functions = 0 -ij_kotlin_wrap_first_method_in_call_chain = false - -[{*.har,*.json}] -indent_size = 2 -ij_json_keep_blank_lines_in_code = 0 -ij_json_keep_indents_on_empty_lines = false -ij_json_keep_line_breaks = true -ij_json_space_after_colon = true -ij_json_space_after_comma = true -ij_json_space_before_colon = true -ij_json_space_before_comma = false -ij_json_spaces_within_braces = false -ij_json_spaces_within_brackets = false -ij_json_wrap_long_lines = false - -[{*.htm,*.html,*.sht,*.shtm,*.shtml}] -ij_html_add_new_line_before_tags = body,div,p,form,h1,h2,h3 -ij_html_align_attributes = true -ij_html_align_text = false -ij_html_attribute_wrap = normal -ij_html_block_comment_at_first_column = true -ij_html_do_not_align_children_of_min_lines = 0 -ij_html_do_not_break_if_inline_tags = title,h1,h2,h3,h4,h5,h6,p -ij_html_do_not_indent_children_of_tags = html,body,thead,tbody,tfoot -ij_html_enforce_quotes = false -ij_html_inline_tags = a,abbr,acronym,b,basefont,bdo,big,br,cite,cite,code,dfn,em,font,i,img,input,kbd,label,q,s,samp,select,small,span,strike,strong,sub,sup,textarea,tt,u,var -ij_html_keep_blank_lines = 2 -ij_html_keep_indents_on_empty_lines = false -ij_html_keep_line_breaks = true -ij_html_keep_line_breaks_in_text = true -ij_html_keep_whitespaces = false -ij_html_keep_whitespaces_inside = span,pre,textarea -ij_html_line_comment_at_first_column = true -ij_html_new_line_after_last_attribute = never -ij_html_new_line_before_first_attribute = never -ij_html_quote_style = double -ij_html_remove_new_line_before_tags = br -ij_html_space_after_tag_name = false -ij_html_space_around_equality_in_attribute = false -ij_html_space_inside_empty_tag = false -ij_html_text_wrap = normal -ij_html_uniform_ident = false - -[{*.yaml,*.yml}] -indent_size = 2 -ij_yaml_align_values_properties = do_not_align -ij_yaml_autoinsert_sequence_marker = true -ij_yaml_block_mapping_on_new_line = false -ij_yaml_indent_sequence_value = true -ij_yaml_keep_indents_on_empty_lines = false -ij_yaml_keep_line_breaks = true -ij_yaml_sequence_on_new_line = false -ij_yaml_space_before_colon = false -ij_yaml_spaces_within_braces = true -ij_yaml_spaces_within_brackets = true +[*] +charset = utf-8 +end_of_line = lf +indent_size = 4 +indent_style = space +insert_final_newline = true +max_line_length = 160 +tab_width = 4 +ij_continuation_indent_size = 8 +ij_formatter_off_tag = @formatter:off +ij_formatter_on_tag = @formatter:on +ij_formatter_tags_enabled = false +ij_smart_tabs = false +ij_visual_guides = none +ij_wrap_on_typing = false + +[*.java] +ij_java_align_consecutive_assignments = false +ij_java_align_consecutive_variable_declarations = false +ij_java_align_group_field_declarations = false +ij_java_align_multiline_annotation_parameters = false +ij_java_align_multiline_array_initializer_expression = false +ij_java_align_multiline_assignment = false +ij_java_align_multiline_binary_operation = false +ij_java_align_multiline_chained_methods = false +ij_java_align_multiline_extends_list = false +ij_java_align_multiline_for = true +ij_java_align_multiline_method_parentheses = false +ij_java_align_multiline_parameters = true +ij_java_align_multiline_parameters_in_calls = false +ij_java_align_multiline_parenthesized_expression = false +ij_java_align_multiline_records = true +ij_java_align_multiline_resources = true +ij_java_align_multiline_ternary_operation = false +ij_java_align_multiline_text_blocks = false +ij_java_align_multiline_throws_list = false +ij_java_align_subsequent_simple_methods = false +ij_java_align_throws_keyword = false +ij_java_annotation_parameter_wrap = off +ij_java_array_initializer_new_line_after_left_brace = false +ij_java_array_initializer_right_brace_on_new_line = false +ij_java_array_initializer_wrap = off +ij_java_assert_statement_colon_on_next_line = false +ij_java_assert_statement_wrap = off +ij_java_assignment_wrap = off +ij_java_binary_operation_sign_on_next_line = false +ij_java_binary_operation_wrap = off +ij_java_blank_lines_after_anonymous_class_header = 0 +ij_java_blank_lines_after_class_header = 0 +ij_java_blank_lines_after_imports = 1 +ij_java_blank_lines_after_package = 1 +ij_java_blank_lines_around_class = 1 +ij_java_blank_lines_around_field = 0 +ij_java_blank_lines_around_field_in_interface = 0 +ij_java_blank_lines_around_initializer = 1 +ij_java_blank_lines_around_method = 1 +ij_java_blank_lines_around_method_in_interface = 1 +ij_java_blank_lines_before_class_end = 0 +ij_java_blank_lines_before_imports = 1 +ij_java_blank_lines_before_method_body = 0 +ij_java_blank_lines_before_package = 0 +ij_java_block_brace_style = end_of_line +ij_java_block_comment_at_first_column = true +ij_java_builder_methods = none +ij_java_call_parameters_new_line_after_left_paren = false +ij_java_call_parameters_right_paren_on_new_line = false +ij_java_call_parameters_wrap = off +ij_java_case_statement_on_separate_line = true +ij_java_catch_on_new_line = false +ij_java_class_annotation_wrap = split_into_lines +ij_java_class_brace_style = end_of_line +ij_java_class_count_to_use_import_on_demand = 99 +ij_java_class_names_in_javadoc = 1 +ij_java_do_not_indent_top_level_class_members = false +ij_java_do_not_wrap_after_single_annotation = false +ij_java_do_while_brace_force = never +ij_java_doc_add_blank_line_after_description = true +ij_java_doc_add_blank_line_after_param_comments = false +ij_java_doc_add_blank_line_after_return = false +ij_java_doc_add_p_tag_on_empty_lines = true +ij_java_doc_align_exception_comments = true +ij_java_doc_align_param_comments = true +ij_java_doc_do_not_wrap_if_one_line = false +ij_java_doc_enable_formatting = true +ij_java_doc_enable_leading_asterisks = true +ij_java_doc_indent_on_continuation = false +ij_java_doc_keep_empty_lines = true +ij_java_doc_keep_empty_parameter_tag = true +ij_java_doc_keep_empty_return_tag = true +ij_java_doc_keep_empty_throws_tag = true +ij_java_doc_keep_invalid_tags = true +ij_java_doc_param_description_on_new_line = false +ij_java_doc_preserve_line_breaks = false +ij_java_doc_use_throws_not_exception_tag = true +ij_java_else_on_new_line = false +ij_java_enum_constants_wrap = off +ij_java_extends_keyword_wrap = off +ij_java_extends_list_wrap = off +ij_java_field_annotation_wrap = split_into_lines +ij_java_finally_on_new_line = false +ij_java_for_brace_force = never +ij_java_for_statement_new_line_after_left_paren = false +ij_java_for_statement_right_paren_on_new_line = false +ij_java_for_statement_wrap = off +ij_java_generate_final_locals = false +ij_java_generate_final_parameters = false +ij_java_if_brace_force = never +ij_java_imports_layout = $android.**,$androidx.**,$com.**,$junit.**,$net.**,$org.**,$java.**,$javax.**,$*,|,android.**,|,androidx.**,|,com.**,|,junit.**,|,net.**,|,org.**,|,java.**,|,javax.**,|,*,| +ij_java_indent_case_from_switch = true +ij_java_insert_inner_class_imports = false +ij_java_insert_override_annotation = true +ij_java_keep_blank_lines_before_right_brace = 2 +ij_java_keep_blank_lines_between_package_declaration_and_header = 2 +ij_java_keep_blank_lines_in_code = 2 +ij_java_keep_blank_lines_in_declarations = 2 +ij_java_keep_builder_methods_indents = false +ij_java_keep_control_statement_in_one_line = true +ij_java_keep_first_column_comment = true +ij_java_keep_indents_on_empty_lines = false +ij_java_keep_line_breaks = true +ij_java_keep_multiple_expressions_in_one_line = false +ij_java_keep_simple_blocks_in_one_line = false +ij_java_keep_simple_classes_in_one_line = false +ij_java_keep_simple_lambdas_in_one_line = false +ij_java_keep_simple_methods_in_one_line = false +ij_java_label_indent_absolute = false +ij_java_label_indent_size = 0 +ij_java_lambda_brace_style = end_of_line +ij_java_layout_static_imports_separately = true +ij_java_line_comment_add_space = false +ij_java_line_comment_at_first_column = true +ij_java_method_annotation_wrap = split_into_lines +ij_java_method_brace_style = end_of_line +ij_java_method_call_chain_wrap = off +ij_java_method_parameters_new_line_after_left_paren = false +ij_java_method_parameters_right_paren_on_new_line = false +ij_java_method_parameters_wrap = off +ij_java_modifier_list_wrap = false +ij_java_names_count_to_use_import_on_demand = 99 +ij_java_new_line_after_lparen_in_record_header = false +ij_java_parameter_annotation_wrap = off +ij_java_parentheses_expression_new_line_after_left_paren = false +ij_java_parentheses_expression_right_paren_on_new_line = false +ij_java_place_assignment_sign_on_next_line = false +ij_java_prefer_longer_names = true +ij_java_prefer_parameters_wrap = false +ij_java_record_components_wrap = normal +ij_java_repeat_synchronized = true +ij_java_replace_instanceof_and_cast = false +ij_java_replace_null_check = true +ij_java_replace_sum_lambda_with_method_ref = true +ij_java_resource_list_new_line_after_left_paren = false +ij_java_resource_list_right_paren_on_new_line = false +ij_java_resource_list_wrap = off +ij_java_rparen_on_new_line_in_record_header = false +ij_java_space_after_closing_angle_bracket_in_type_argument = false +ij_java_space_after_colon = true +ij_java_space_after_comma = true +ij_java_space_after_comma_in_type_arguments = true +ij_java_space_after_for_semicolon = true +ij_java_space_after_quest = true +ij_java_space_after_type_cast = true +ij_java_space_before_annotation_array_initializer_left_brace = false +ij_java_space_before_annotation_parameter_list = false +ij_java_space_before_array_initializer_left_brace = false +ij_java_space_before_catch_keyword = true +ij_java_space_before_catch_left_brace = true +ij_java_space_before_catch_parentheses = true +ij_java_space_before_class_left_brace = true +ij_java_space_before_colon = true +ij_java_space_before_colon_in_foreach = true +ij_java_space_before_comma = false +ij_java_space_before_do_left_brace = true +ij_java_space_before_else_keyword = true +ij_java_space_before_else_left_brace = true +ij_java_space_before_finally_keyword = true +ij_java_space_before_finally_left_brace = true +ij_java_space_before_for_left_brace = true +ij_java_space_before_for_parentheses = true +ij_java_space_before_for_semicolon = false +ij_java_space_before_if_left_brace = true +ij_java_space_before_if_parentheses = true +ij_java_space_before_method_call_parentheses = false +ij_java_space_before_method_left_brace = true +ij_java_space_before_method_parentheses = false +ij_java_space_before_opening_angle_bracket_in_type_parameter = false +ij_java_space_before_quest = true +ij_java_space_before_switch_left_brace = true +ij_java_space_before_switch_parentheses = true +ij_java_space_before_synchronized_left_brace = true +ij_java_space_before_synchronized_parentheses = true +ij_java_space_before_try_left_brace = true +ij_java_space_before_try_parentheses = true +ij_java_space_before_type_parameter_list = false +ij_java_space_before_while_keyword = true +ij_java_space_before_while_left_brace = true +ij_java_space_before_while_parentheses = true +ij_java_space_inside_one_line_enum_braces = false +ij_java_space_within_empty_array_initializer_braces = false +ij_java_space_within_empty_method_call_parentheses = false +ij_java_space_within_empty_method_parentheses = false +ij_java_spaces_around_additive_operators = true +ij_java_spaces_around_assignment_operators = true +ij_java_spaces_around_bitwise_operators = true +ij_java_spaces_around_equality_operators = true +ij_java_spaces_around_lambda_arrow = true +ij_java_spaces_around_logical_operators = true +ij_java_spaces_around_method_ref_dbl_colon = false +ij_java_spaces_around_multiplicative_operators = true +ij_java_spaces_around_relational_operators = true +ij_java_spaces_around_shift_operators = true +ij_java_spaces_around_type_bounds_in_type_parameters = true +ij_java_spaces_around_unary_operator = false +ij_java_spaces_within_angle_brackets = false +ij_java_spaces_within_annotation_parentheses = false +ij_java_spaces_within_array_initializer_braces = false +ij_java_spaces_within_braces = false +ij_java_spaces_within_brackets = false +ij_java_spaces_within_cast_parentheses = false +ij_java_spaces_within_catch_parentheses = false +ij_java_spaces_within_for_parentheses = false +ij_java_spaces_within_if_parentheses = false +ij_java_spaces_within_method_call_parentheses = false +ij_java_spaces_within_method_parentheses = false +ij_java_spaces_within_parentheses = false +ij_java_spaces_within_record_header = false +ij_java_spaces_within_switch_parentheses = false +ij_java_spaces_within_synchronized_parentheses = false +ij_java_spaces_within_try_parentheses = false +ij_java_spaces_within_while_parentheses = false +ij_java_special_else_if_treatment = true +ij_java_subclass_name_suffix = Impl +ij_java_ternary_operation_signs_on_next_line = false +ij_java_ternary_operation_wrap = off +ij_java_test_name_suffix = Test +ij_java_throws_keyword_wrap = off +ij_java_throws_list_wrap = off +ij_java_use_external_annotations = false +ij_java_use_fq_class_names = false +ij_java_use_relative_indents = false +ij_java_use_single_class_imports = true +ij_java_variable_annotation_wrap = off +ij_java_visibility = public +ij_java_while_brace_force = never +ij_java_while_on_new_line = false +ij_java_wrap_comments = false +ij_java_wrap_first_method_in_call_chain = false +ij_java_wrap_long_lines = false + +[*.properties] +ij_properties_align_group_field_declarations = false +ij_properties_keep_blank_lines = false +ij_properties_key_value_delimiter = equals +ij_properties_spaces_around_key_value_delimiter = false + +[.editorconfig] +ij_editorconfig_align_group_field_declarations = false +ij_editorconfig_space_after_colon = false +ij_editorconfig_space_after_comma = true +ij_editorconfig_space_before_colon = false +ij_editorconfig_space_before_comma = false +ij_editorconfig_spaces_around_assignment_operators = true + +[{*.ant,*.fxml,*.jhm,*.jnlp,*.jrxml,*.rng,*.tld,*.wsdl,*.xml,*.xsd,*.xsl,*.xslt,*.xul}] +ij_continuation_indent_size = 4 +ij_xml_align_attributes = false +ij_xml_align_text = false +ij_xml_attribute_wrap = normal +ij_xml_block_comment_at_first_column = true +ij_xml_keep_blank_lines = 2 +ij_xml_keep_indents_on_empty_lines = false +ij_xml_keep_line_breaks = false +ij_xml_keep_line_breaks_in_text = true +ij_xml_keep_whitespaces = false +ij_xml_keep_whitespaces_around_cdata = preserve +ij_xml_keep_whitespaces_inside_cdata = false +ij_xml_line_comment_at_first_column = true +ij_xml_space_after_tag_name = false +ij_xml_space_around_equals_in_attribute = false +ij_xml_space_inside_empty_tag = true +ij_xml_text_wrap = normal +ij_xml_use_custom_settings = true + +[{*.bash,*.sh,*.zsh}] +indent_size = 2 +tab_width = 2 +ij_shell_binary_ops_start_line = false +ij_shell_keep_column_alignment_padding = false +ij_shell_minify_program = false +ij_shell_redirect_followed_by_space = false +ij_shell_switch_cases_indented = false +ij_shell_use_unix_line_separator = true + +[{*.c,*.c++,*.cc,*.cp,*.cpp,*.cu,*.cuh,*.cxx,*.h,*.h++,*.hh,*.hp,*.hpp,*.hxx,*.i,*.icc,*.ii,*.inl,*.ino,*.ipp,*.m,*.mm,*.pch,*.tcc,*.tpp}] +ij_c_add_brief_tag = false +ij_c_add_getter_prefix = true +ij_c_add_setter_prefix = true +ij_c_align_dictionary_pair_values = false +ij_c_align_group_field_declarations = false +ij_c_align_init_list_in_columns = true +ij_c_align_multiline_array_initializer_expression = true +ij_c_align_multiline_assignment = true +ij_c_align_multiline_binary_operation = true +ij_c_align_multiline_chained_methods = false +ij_c_align_multiline_for = true +ij_c_align_multiline_ternary_operation = true +ij_c_array_initializer_comma_on_next_line = false +ij_c_array_initializer_new_line_after_left_brace = false +ij_c_array_initializer_right_brace_on_new_line = false +ij_c_array_initializer_wrap = normal +ij_c_assignment_wrap = off +ij_c_binary_operation_sign_on_next_line = false +ij_c_binary_operation_wrap = normal +ij_c_blank_lines_after_class_header = 0 +ij_c_blank_lines_after_imports = 1 +ij_c_blank_lines_around_class = 1 +ij_c_blank_lines_around_field = 0 +ij_c_blank_lines_around_field_in_interface = 0 +ij_c_blank_lines_around_method = 1 +ij_c_blank_lines_around_method_in_interface = 1 +ij_c_blank_lines_around_namespace = 0 +ij_c_blank_lines_around_properties_in_declaration = 0 +ij_c_blank_lines_around_properties_in_interface = 0 +ij_c_blank_lines_before_imports = 1 +ij_c_blank_lines_before_method_body = 0 +ij_c_block_brace_placement = end_of_line +ij_c_block_brace_style = end_of_line +ij_c_block_comment_at_first_column = true +ij_c_catch_on_new_line = false +ij_c_class_brace_style = end_of_line +ij_c_class_constructor_init_list_align_multiline = true +ij_c_class_constructor_init_list_comma_on_next_line = false +ij_c_class_constructor_init_list_new_line_after_colon = never +ij_c_class_constructor_init_list_new_line_before_colon = if_long +ij_c_class_constructor_init_list_wrap = normal +ij_c_copy_is_deep = false +ij_c_create_interface_for_categories = true +ij_c_declare_generated_methods = true +ij_c_description_include_member_names = true +ij_c_discharged_short_ternary_operator = false +ij_c_do_not_add_breaks = false +ij_c_do_while_brace_force = never +ij_c_else_on_new_line = false +ij_c_enum_constants_comma_on_next_line = false +ij_c_enum_constants_wrap = on_every_item +ij_c_for_brace_force = never +ij_c_for_statement_new_line_after_left_paren = false +ij_c_for_statement_right_paren_on_new_line = false +ij_c_for_statement_wrap = off +ij_c_function_brace_placement = end_of_line +ij_c_function_call_arguments_align_multiline = true +ij_c_function_call_arguments_align_multiline_pars = false +ij_c_function_call_arguments_comma_on_next_line = false +ij_c_function_call_arguments_new_line_after_lpar = false +ij_c_function_call_arguments_new_line_before_rpar = false +ij_c_function_call_arguments_wrap = normal +ij_c_function_non_top_after_return_type_wrap = normal +ij_c_function_parameters_align_multiline = true +ij_c_function_parameters_align_multiline_pars = false +ij_c_function_parameters_comma_on_next_line = false +ij_c_function_parameters_new_line_after_lpar = false +ij_c_function_parameters_new_line_before_rpar = false +ij_c_function_parameters_wrap = normal +ij_c_function_top_after_return_type_wrap = normal +ij_c_generate_additional_eq_operators = true +ij_c_generate_additional_rel_operators = true +ij_c_generate_class_constructor = true +ij_c_generate_comparison_operators_use_std_tie = false +ij_c_generate_instance_variables_for_properties = ask +ij_c_generate_operators_as_members = true +ij_c_header_guard_style_pattern = ${PROJECT_NAME}_${FILE_NAME}_${EXT} +ij_c_if_brace_force = never +ij_c_in_line_short_ternary_operator = true +ij_c_indent_block_comment = true +ij_c_indent_c_struct_members = 4 +ij_c_indent_case_from_switch = true +ij_c_indent_class_members = 4 +ij_c_indent_directive_as_code = false +ij_c_indent_implementation_members = 0 +ij_c_indent_inside_code_block = 4 +ij_c_indent_interface_members = 0 +ij_c_indent_interface_members_except_ivars_block = false +ij_c_indent_namespace_members = 4 +ij_c_indent_preprocessor_directive = 0 +ij_c_indent_visibility_keywords = 0 +ij_c_insert_override = true +ij_c_insert_virtual_with_override = false +ij_c_introduce_auto_vars = false +ij_c_introduce_const_params = false +ij_c_introduce_const_vars = false +ij_c_introduce_generate_property = false +ij_c_introduce_generate_synthesize = true +ij_c_introduce_globals_to_header = true +ij_c_introduce_prop_to_private_category = false +ij_c_introduce_static_consts = true +ij_c_introduce_use_ns_types = false +ij_c_ivars_prefix = _ +ij_c_keep_blank_lines_before_end = 2 +ij_c_keep_blank_lines_before_right_brace = 2 +ij_c_keep_blank_lines_in_code = 2 +ij_c_keep_blank_lines_in_declarations = 2 +ij_c_keep_case_expressions_in_one_line = false +ij_c_keep_control_statement_in_one_line = true +ij_c_keep_directive_at_first_column = true +ij_c_keep_first_column_comment = true +ij_c_keep_line_breaks = true +ij_c_keep_nested_namespaces_in_one_line = false +ij_c_keep_simple_blocks_in_one_line = true +ij_c_keep_simple_methods_in_one_line = true +ij_c_keep_structures_in_one_line = false +ij_c_lambda_capture_list_align_multiline = false +ij_c_lambda_capture_list_align_multiline_bracket = false +ij_c_lambda_capture_list_comma_on_next_line = false +ij_c_lambda_capture_list_new_line_after_lbracket = false +ij_c_lambda_capture_list_new_line_before_rbracket = false +ij_c_lambda_capture_list_wrap = off +ij_c_line_comment_add_space = false +ij_c_line_comment_at_first_column = true +ij_c_method_brace_placement = end_of_line +ij_c_method_call_arguments_align_by_colons = true +ij_c_method_call_arguments_align_multiline = false +ij_c_method_call_arguments_special_dictionary_pairs_treatment = true +ij_c_method_call_arguments_wrap = off +ij_c_method_call_chain_wrap = off +ij_c_method_parameters_align_by_colons = true +ij_c_method_parameters_align_multiline = false +ij_c_method_parameters_wrap = off +ij_c_namespace_brace_placement = end_of_line +ij_c_parentheses_expression_new_line_after_left_paren = false +ij_c_parentheses_expression_right_paren_on_new_line = false +ij_c_place_assignment_sign_on_next_line = false +ij_c_property_nonatomic = true +ij_c_put_ivars_to_implementation = true +ij_c_refactor_compatibility_aliases_and_classes = true +ij_c_refactor_properties_and_ivars = true +ij_c_release_style = ivar +ij_c_retain_object_parameters_in_constructor = true +ij_c_semicolon_after_method_signature = false +ij_c_shift_operation_align_multiline = true +ij_c_shift_operation_wrap = normal +ij_c_show_non_virtual_functions = false +ij_c_space_after_colon = true +ij_c_space_after_colon_in_selector = false +ij_c_space_after_comma = true +ij_c_space_after_cup_in_blocks = false +ij_c_space_after_dictionary_literal_colon = true +ij_c_space_after_for_semicolon = true +ij_c_space_after_init_list_colon = true +ij_c_space_after_method_parameter_type_parentheses = false +ij_c_space_after_method_return_type_parentheses = false +ij_c_space_after_pointer_in_declaration = false +ij_c_space_after_quest = true +ij_c_space_after_reference_in_declaration = false +ij_c_space_after_reference_in_rvalue = false +ij_c_space_after_structures_rbrace = true +ij_c_space_after_superclass_colon = true +ij_c_space_after_type_cast = true +ij_c_space_after_visibility_sign_in_method_declaration = true +ij_c_space_before_autorelease_pool_lbrace = true +ij_c_space_before_catch_keyword = true +ij_c_space_before_catch_left_brace = true +ij_c_space_before_catch_parentheses = true +ij_c_space_before_category_parentheses = true +ij_c_space_before_chained_send_message = true +ij_c_space_before_class_left_brace = true +ij_c_space_before_colon = true +ij_c_space_before_comma = false +ij_c_space_before_dictionary_literal_colon = false +ij_c_space_before_do_left_brace = true +ij_c_space_before_else_keyword = true +ij_c_space_before_else_left_brace = true +ij_c_space_before_for_left_brace = true +ij_c_space_before_for_parentheses = true +ij_c_space_before_for_semicolon = false +ij_c_space_before_if_left_brace = true +ij_c_space_before_if_parentheses = true +ij_c_space_before_init_list = false +ij_c_space_before_init_list_colon = true +ij_c_space_before_method_call_parentheses = false +ij_c_space_before_method_left_brace = true +ij_c_space_before_method_parentheses = false +ij_c_space_before_namespace_lbrace = true +ij_c_space_before_pointer_in_declaration = true +ij_c_space_before_property_attributes_parentheses = false +ij_c_space_before_protocols_brackets = true +ij_c_space_before_quest = true +ij_c_space_before_reference_in_declaration = true +ij_c_space_before_superclass_colon = true +ij_c_space_before_switch_left_brace = true +ij_c_space_before_switch_parentheses = true +ij_c_space_before_template_call_lt = false +ij_c_space_before_template_declaration_lt = false +ij_c_space_before_try_left_brace = true +ij_c_space_before_while_keyword = true +ij_c_space_before_while_left_brace = true +ij_c_space_before_while_parentheses = true +ij_c_space_between_adjacent_brackets = false +ij_c_space_between_operator_and_punctuator = false +ij_c_space_within_empty_array_initializer_braces = false +ij_c_spaces_around_additive_operators = true +ij_c_spaces_around_assignment_operators = true +ij_c_spaces_around_bitwise_operators = true +ij_c_spaces_around_equality_operators = true +ij_c_spaces_around_lambda_arrow = true +ij_c_spaces_around_logical_operators = true +ij_c_spaces_around_multiplicative_operators = true +ij_c_spaces_around_pm_operators = false +ij_c_spaces_around_relational_operators = true +ij_c_spaces_around_shift_operators = true +ij_c_spaces_around_unary_operator = false +ij_c_spaces_within_array_initializer_braces = false +ij_c_spaces_within_braces = true +ij_c_spaces_within_brackets = false +ij_c_spaces_within_cast_parentheses = false +ij_c_spaces_within_catch_parentheses = false +ij_c_spaces_within_category_parentheses = false +ij_c_spaces_within_empty_braces = false +ij_c_spaces_within_empty_function_call_parentheses = false +ij_c_spaces_within_empty_function_declaration_parentheses = false +ij_c_spaces_within_empty_lambda_capture_list_bracket = false +ij_c_spaces_within_empty_template_call_ltgt = false +ij_c_spaces_within_empty_template_declaration_ltgt = false +ij_c_spaces_within_for_parentheses = false +ij_c_spaces_within_function_call_parentheses = false +ij_c_spaces_within_function_declaration_parentheses = false +ij_c_spaces_within_if_parentheses = false +ij_c_spaces_within_lambda_capture_list_bracket = false +ij_c_spaces_within_method_parameter_type_parentheses = false +ij_c_spaces_within_method_return_type_parentheses = false +ij_c_spaces_within_parentheses = false +ij_c_spaces_within_property_attributes_parentheses = false +ij_c_spaces_within_protocols_brackets = false +ij_c_spaces_within_send_message_brackets = false +ij_c_spaces_within_switch_parentheses = false +ij_c_spaces_within_template_call_ltgt = false +ij_c_spaces_within_template_declaration_ltgt = false +ij_c_spaces_within_template_double_gt = true +ij_c_spaces_within_while_parentheses = false +ij_c_special_else_if_treatment = true +ij_c_superclass_list_after_colon = never +ij_c_superclass_list_align_multiline = true +ij_c_superclass_list_before_colon = if_long +ij_c_superclass_list_comma_on_next_line = false +ij_c_superclass_list_wrap = on_every_item +ij_c_tag_prefix_of_block_comment = at +ij_c_tag_prefix_of_line_comment = back_slash +ij_c_template_call_arguments_align_multiline = false +ij_c_template_call_arguments_align_multiline_pars = false +ij_c_template_call_arguments_comma_on_next_line = false +ij_c_template_call_arguments_new_line_after_lt = false +ij_c_template_call_arguments_new_line_before_gt = false +ij_c_template_call_arguments_wrap = off +ij_c_template_declaration_function_body_indent = false +ij_c_template_declaration_function_wrap = split_into_lines +ij_c_template_declaration_struct_body_indent = false +ij_c_template_declaration_struct_wrap = split_into_lines +ij_c_template_parameters_align_multiline = false +ij_c_template_parameters_align_multiline_pars = false +ij_c_template_parameters_comma_on_next_line = false +ij_c_template_parameters_new_line_after_lt = false +ij_c_template_parameters_new_line_before_gt = false +ij_c_template_parameters_wrap = off +ij_c_ternary_operation_signs_on_next_line = true +ij_c_ternary_operation_wrap = normal +ij_c_type_qualifiers_placement = before +ij_c_use_modern_casts = true +ij_c_use_setters_in_constructor = true +ij_c_while_brace_force = never +ij_c_while_on_new_line = false +ij_c_wrap_property_declaration = off + +[{*.cmake,CMakeLists.txt}] +ij_cmake_align_multiline_parameters_in_calls = false +ij_cmake_force_commands_case = 2 +ij_cmake_keep_blank_lines_in_code = 2 +ij_cmake_space_before_for_parentheses = true +ij_cmake_space_before_if_parentheses = true +ij_cmake_space_before_method_call_parentheses = false +ij_cmake_space_before_method_parentheses = false +ij_cmake_space_before_while_parentheses = true +ij_cmake_spaces_within_for_parentheses = false +ij_cmake_spaces_within_if_parentheses = false +ij_cmake_spaces_within_method_call_parentheses = false +ij_cmake_spaces_within_method_parentheses = false +ij_cmake_spaces_within_while_parentheses = false + +[{*.gant,*.gradle,*.groovy,*.gy}] +ij_groovy_align_group_field_declarations = false +ij_groovy_align_multiline_array_initializer_expression = false +ij_groovy_align_multiline_assignment = false +ij_groovy_align_multiline_binary_operation = false +ij_groovy_align_multiline_chained_methods = false +ij_groovy_align_multiline_extends_list = false +ij_groovy_align_multiline_for = true +ij_groovy_align_multiline_list_or_map = true +ij_groovy_align_multiline_method_parentheses = false +ij_groovy_align_multiline_parameters = true +ij_groovy_align_multiline_parameters_in_calls = false +ij_groovy_align_multiline_resources = true +ij_groovy_align_multiline_ternary_operation = false +ij_groovy_align_multiline_throws_list = false +ij_groovy_align_named_args_in_map = true +ij_groovy_align_throws_keyword = false +ij_groovy_array_initializer_new_line_after_left_brace = false +ij_groovy_array_initializer_right_brace_on_new_line = false +ij_groovy_array_initializer_wrap = off +ij_groovy_assert_statement_wrap = off +ij_groovy_assignment_wrap = off +ij_groovy_binary_operation_wrap = off +ij_groovy_blank_lines_after_class_header = 0 +ij_groovy_blank_lines_after_imports = 1 +ij_groovy_blank_lines_after_package = 1 +ij_groovy_blank_lines_around_class = 1 +ij_groovy_blank_lines_around_field = 0 +ij_groovy_blank_lines_around_field_in_interface = 0 +ij_groovy_blank_lines_around_method = 1 +ij_groovy_blank_lines_around_method_in_interface = 1 +ij_groovy_blank_lines_before_imports = 1 +ij_groovy_blank_lines_before_method_body = 0 +ij_groovy_blank_lines_before_package = 0 +ij_groovy_block_brace_style = end_of_line +ij_groovy_block_comment_at_first_column = true +ij_groovy_call_parameters_new_line_after_left_paren = false +ij_groovy_call_parameters_right_paren_on_new_line = false +ij_groovy_call_parameters_wrap = off +ij_groovy_catch_on_new_line = false +ij_groovy_class_annotation_wrap = split_into_lines +ij_groovy_class_brace_style = end_of_line +ij_groovy_class_count_to_use_import_on_demand = 5 +ij_groovy_do_while_brace_force = never +ij_groovy_else_on_new_line = false +ij_groovy_enum_constants_wrap = off +ij_groovy_extends_keyword_wrap = off +ij_groovy_extends_list_wrap = off +ij_groovy_field_annotation_wrap = split_into_lines +ij_groovy_finally_on_new_line = false +ij_groovy_for_brace_force = never +ij_groovy_for_statement_new_line_after_left_paren = false +ij_groovy_for_statement_right_paren_on_new_line = false +ij_groovy_for_statement_wrap = off +ij_groovy_if_brace_force = never +ij_groovy_import_annotation_wrap = 2 +ij_groovy_imports_layout = *,|,javax.**,java.**,|,$* +ij_groovy_indent_case_from_switch = true +ij_groovy_indent_label_blocks = true +ij_groovy_insert_inner_class_imports = false +ij_groovy_keep_blank_lines_before_right_brace = 2 +ij_groovy_keep_blank_lines_in_code = 2 +ij_groovy_keep_blank_lines_in_declarations = 2 +ij_groovy_keep_control_statement_in_one_line = true +ij_groovy_keep_first_column_comment = true +ij_groovy_keep_indents_on_empty_lines = false +ij_groovy_keep_line_breaks = true +ij_groovy_keep_multiple_expressions_in_one_line = false +ij_groovy_keep_simple_blocks_in_one_line = false +ij_groovy_keep_simple_classes_in_one_line = true +ij_groovy_keep_simple_lambdas_in_one_line = true +ij_groovy_keep_simple_methods_in_one_line = true +ij_groovy_label_indent_absolute = false +ij_groovy_label_indent_size = 0 +ij_groovy_lambda_brace_style = end_of_line +ij_groovy_layout_static_imports_separately = true +ij_groovy_line_comment_add_space = false +ij_groovy_line_comment_at_first_column = true +ij_groovy_method_annotation_wrap = split_into_lines +ij_groovy_method_brace_style = end_of_line +ij_groovy_method_call_chain_wrap = off +ij_groovy_method_parameters_new_line_after_left_paren = false +ij_groovy_method_parameters_right_paren_on_new_line = false +ij_groovy_method_parameters_wrap = off +ij_groovy_modifier_list_wrap = false +ij_groovy_names_count_to_use_import_on_demand = 3 +ij_groovy_parameter_annotation_wrap = off +ij_groovy_parentheses_expression_new_line_after_left_paren = false +ij_groovy_parentheses_expression_right_paren_on_new_line = false +ij_groovy_prefer_parameters_wrap = false +ij_groovy_resource_list_new_line_after_left_paren = false +ij_groovy_resource_list_right_paren_on_new_line = false +ij_groovy_resource_list_wrap = off +ij_groovy_space_after_assert_separator = true +ij_groovy_space_after_colon = true +ij_groovy_space_after_comma = true +ij_groovy_space_after_comma_in_type_arguments = true +ij_groovy_space_after_for_semicolon = true +ij_groovy_space_after_quest = true +ij_groovy_space_after_type_cast = true +ij_groovy_space_before_annotation_parameter_list = false +ij_groovy_space_before_array_initializer_left_brace = false +ij_groovy_space_before_assert_separator = false +ij_groovy_space_before_catch_keyword = true +ij_groovy_space_before_catch_left_brace = true +ij_groovy_space_before_catch_parentheses = true +ij_groovy_space_before_class_left_brace = true +ij_groovy_space_before_closure_left_brace = true +ij_groovy_space_before_colon = true +ij_groovy_space_before_comma = false +ij_groovy_space_before_do_left_brace = true +ij_groovy_space_before_else_keyword = true +ij_groovy_space_before_else_left_brace = true +ij_groovy_space_before_finally_keyword = true +ij_groovy_space_before_finally_left_brace = true +ij_groovy_space_before_for_left_brace = true +ij_groovy_space_before_for_parentheses = true +ij_groovy_space_before_for_semicolon = false +ij_groovy_space_before_if_left_brace = true +ij_groovy_space_before_if_parentheses = true +ij_groovy_space_before_method_call_parentheses = false +ij_groovy_space_before_method_left_brace = true +ij_groovy_space_before_method_parentheses = false +ij_groovy_space_before_quest = true +ij_groovy_space_before_switch_left_brace = true +ij_groovy_space_before_switch_parentheses = true +ij_groovy_space_before_synchronized_left_brace = true +ij_groovy_space_before_synchronized_parentheses = true +ij_groovy_space_before_try_left_brace = true +ij_groovy_space_before_try_parentheses = true +ij_groovy_space_before_while_keyword = true +ij_groovy_space_before_while_left_brace = true +ij_groovy_space_before_while_parentheses = true +ij_groovy_space_in_named_argument = true +ij_groovy_space_in_named_argument_before_colon = false +ij_groovy_space_within_empty_array_initializer_braces = false +ij_groovy_space_within_empty_method_call_parentheses = false +ij_groovy_spaces_around_additive_operators = true +ij_groovy_spaces_around_assignment_operators = true +ij_groovy_spaces_around_bitwise_operators = true +ij_groovy_spaces_around_equality_operators = true +ij_groovy_spaces_around_lambda_arrow = true +ij_groovy_spaces_around_logical_operators = true +ij_groovy_spaces_around_multiplicative_operators = true +ij_groovy_spaces_around_regex_operators = true +ij_groovy_spaces_around_relational_operators = true +ij_groovy_spaces_around_shift_operators = true +ij_groovy_spaces_within_annotation_parentheses = false +ij_groovy_spaces_within_array_initializer_braces = false +ij_groovy_spaces_within_braces = true +ij_groovy_spaces_within_brackets = false +ij_groovy_spaces_within_cast_parentheses = false +ij_groovy_spaces_within_catch_parentheses = false +ij_groovy_spaces_within_for_parentheses = false +ij_groovy_spaces_within_gstring_injection_braces = false +ij_groovy_spaces_within_if_parentheses = false +ij_groovy_spaces_within_list_or_map = false +ij_groovy_spaces_within_method_call_parentheses = false +ij_groovy_spaces_within_method_parentheses = false +ij_groovy_spaces_within_parentheses = false +ij_groovy_spaces_within_switch_parentheses = false +ij_groovy_spaces_within_synchronized_parentheses = false +ij_groovy_spaces_within_try_parentheses = false +ij_groovy_spaces_within_tuple_expression = false +ij_groovy_spaces_within_while_parentheses = false +ij_groovy_special_else_if_treatment = true +ij_groovy_ternary_operation_wrap = off +ij_groovy_throws_keyword_wrap = off +ij_groovy_throws_list_wrap = off +ij_groovy_use_flying_geese_braces = false +ij_groovy_use_fq_class_names = false +ij_groovy_use_fq_class_names_in_javadoc = true +ij_groovy_use_relative_indents = false +ij_groovy_use_single_class_imports = true +ij_groovy_variable_annotation_wrap = off +ij_groovy_while_brace_force = never +ij_groovy_while_on_new_line = false +ij_groovy_wrap_long_lines = false + +[{*.gradle.kts,*.kt,*.kts,*.main.kts}] +ij_kotlin_align_in_columns_case_branch = true +ij_kotlin_align_multiline_binary_operation = false +ij_kotlin_align_multiline_extends_list = false +ij_kotlin_align_multiline_method_parentheses = false +ij_kotlin_align_multiline_parameters = true +ij_kotlin_align_multiline_parameters_in_calls = false +ij_kotlin_allow_trailing_comma = false +ij_kotlin_allow_trailing_comma_on_call_site = false +ij_kotlin_assignment_wrap = off +ij_kotlin_blank_lines_after_class_header = 0 +ij_kotlin_blank_lines_around_block_when_branches = 0 +ij_kotlin_blank_lines_before_declaration_with_comment_or_annotation_on_separate_line = 1 +ij_kotlin_block_comment_at_first_column = true +ij_kotlin_call_parameters_new_line_after_left_paren = false +ij_kotlin_call_parameters_right_paren_on_new_line = false +ij_kotlin_call_parameters_wrap = off +ij_kotlin_catch_on_new_line = false +ij_kotlin_class_annotation_wrap = off +ij_kotlin_code_style_defaults = KOTLIN_OFFICIAL +ij_kotlin_continuation_indent_for_chained_calls = true +ij_kotlin_continuation_indent_for_expression_bodies = true +ij_kotlin_continuation_indent_in_argument_lists = true +ij_kotlin_continuation_indent_in_elvis = true +ij_kotlin_continuation_indent_in_if_conditions = true +ij_kotlin_continuation_indent_in_parameter_lists = true +ij_kotlin_continuation_indent_in_supertype_lists = true +ij_kotlin_else_on_new_line = false +ij_kotlin_enum_constants_wrap = off +ij_kotlin_extends_list_wrap = off +ij_kotlin_field_annotation_wrap = normal +ij_kotlin_finally_on_new_line = false +ij_kotlin_if_rparen_on_new_line = false +ij_kotlin_import_nested_classes = false +ij_kotlin_imports_layout = *,java.**,javax.**,kotlin.**,^ +ij_kotlin_insert_whitespaces_in_simple_one_line_method = true +ij_kotlin_keep_blank_lines_before_right_brace = 0 +ij_kotlin_keep_blank_lines_in_code = 1 +ij_kotlin_keep_blank_lines_in_declarations = 1 +ij_kotlin_keep_first_column_comment = true +ij_kotlin_keep_indents_on_empty_lines = false +ij_kotlin_keep_line_breaks = true +ij_kotlin_lbrace_on_next_line = false +ij_kotlin_line_comment_add_space = false +ij_kotlin_line_comment_at_first_column = true +ij_kotlin_method_annotation_wrap = split_into_lines +ij_kotlin_method_call_chain_wrap = off +ij_kotlin_method_parameters_new_line_after_left_paren = false +ij_kotlin_method_parameters_right_paren_on_new_line = false +ij_kotlin_method_parameters_wrap = off +ij_kotlin_name_count_to_use_star_import = 2147483647 +ij_kotlin_name_count_to_use_star_import_for_members = 2147483647 +ij_kotlin_packages_to_use_import_on_demand = kotlinx.android.synthetic.** +ij_kotlin_parameter_annotation_wrap = off +ij_kotlin_space_after_comma = true +ij_kotlin_space_after_extend_colon = true +ij_kotlin_space_after_type_colon = true +ij_kotlin_space_before_catch_parentheses = true +ij_kotlin_space_before_comma = false +ij_kotlin_space_before_extend_colon = true +ij_kotlin_space_before_for_parentheses = true +ij_kotlin_space_before_if_parentheses = true +ij_kotlin_space_before_lambda_arrow = true +ij_kotlin_space_before_type_colon = false +ij_kotlin_space_before_when_parentheses = true +ij_kotlin_space_before_while_parentheses = true +ij_kotlin_spaces_around_additive_operators = true +ij_kotlin_spaces_around_assignment_operators = true +ij_kotlin_spaces_around_equality_operators = true +ij_kotlin_spaces_around_function_type_arrow = true +ij_kotlin_spaces_around_logical_operators = true +ij_kotlin_spaces_around_multiplicative_operators = true +ij_kotlin_spaces_around_range = false +ij_kotlin_spaces_around_relational_operators = true +ij_kotlin_spaces_around_unary_operator = false +ij_kotlin_spaces_around_when_arrow = true +ij_kotlin_use_custom_formatting_for_modifiers = true +ij_kotlin_variable_annotation_wrap = off +ij_kotlin_while_on_new_line = false +ij_kotlin_wrap_elvis_expressions = 1 +ij_kotlin_wrap_expression_body_functions = 0 +ij_kotlin_wrap_first_method_in_call_chain = false + +[{*.har,*.json}] +indent_size = 2 +ij_json_keep_blank_lines_in_code = 0 +ij_json_keep_indents_on_empty_lines = false +ij_json_keep_line_breaks = true +ij_json_space_after_colon = true +ij_json_space_after_comma = true +ij_json_space_before_colon = true +ij_json_space_before_comma = false +ij_json_spaces_within_braces = false +ij_json_spaces_within_brackets = false +ij_json_wrap_long_lines = false + +[{*.htm,*.html,*.sht,*.shtm,*.shtml}] +ij_html_add_new_line_before_tags = body,div,p,form,h1,h2,h3 +ij_html_align_attributes = true +ij_html_align_text = false +ij_html_attribute_wrap = normal +ij_html_block_comment_at_first_column = true +ij_html_do_not_align_children_of_min_lines = 0 +ij_html_do_not_break_if_inline_tags = title,h1,h2,h3,h4,h5,h6,p +ij_html_do_not_indent_children_of_tags = html,body,thead,tbody,tfoot +ij_html_enforce_quotes = false +ij_html_inline_tags = a,abbr,acronym,b,basefont,bdo,big,br,cite,cite,code,dfn,em,font,i,img,input,kbd,label,q,s,samp,select,small,span,strike,strong,sub,sup,textarea,tt,u,var +ij_html_keep_blank_lines = 2 +ij_html_keep_indents_on_empty_lines = false +ij_html_keep_line_breaks = true +ij_html_keep_line_breaks_in_text = true +ij_html_keep_whitespaces = false +ij_html_keep_whitespaces_inside = span,pre,textarea +ij_html_line_comment_at_first_column = true +ij_html_new_line_after_last_attribute = never +ij_html_new_line_before_first_attribute = never +ij_html_quote_style = double +ij_html_remove_new_line_before_tags = br +ij_html_space_after_tag_name = false +ij_html_space_around_equality_in_attribute = false +ij_html_space_inside_empty_tag = false +ij_html_text_wrap = normal +ij_html_uniform_ident = false + +[{*.yaml,*.yml}] +indent_size = 2 +ij_yaml_align_values_properties = do_not_align +ij_yaml_autoinsert_sequence_marker = true +ij_yaml_block_mapping_on_new_line = false +ij_yaml_indent_sequence_value = true +ij_yaml_keep_indents_on_empty_lines = false +ij_yaml_keep_line_breaks = true +ij_yaml_sequence_on_new_line = false +ij_yaml_space_before_colon = false +ij_yaml_spaces_within_braces = true +ij_yaml_spaces_within_brackets = true diff --git a/.github/ISSUE_TEMPLATE/release.yml b/.github/ISSUE_TEMPLATE/release.yml index c22b00e2e9..7cb47fa952 100644 --- a/.github/ISSUE_TEMPLATE/release.yml +++ b/.github/ISSUE_TEMPLATE/release.yml @@ -23,7 +23,8 @@ body: ### Do the release - - [ ] Create release with gitflow, branch name `release/1.2.3` + - [ ] Make sure `develop` and `main` are up to date (git pull) + - [ ] Checkout develop and create a release with gitflow, branch name `release/1.2.3` - [ ] Check the crashes from the PlayStore - [ ] Check the rageshake with the current dev version: https://github.com/matrix-org/element-android-rageshakes/labels/1.2.3-dev - [ ] Run the integration test, and especially `UiAllScreensSanityTest.allScreensTest()` @@ -106,7 +107,7 @@ body: https://github.com/matrix-org/matrix-android-sdk2-sample - - [ ] Update the dependency to the new version of the SDK2. It can take some time for MavenCentral to make the librarie available. You can check status on https://repo1.maven.org/maven2/org/matrix/android/matrix-android-sdk2/ + - [ ] Update the dependency to the new version of the SDK2. It can take a few minutes for MavenCentral to make the library available. You can check status on https://repo1.maven.org/maven2/org/matrix/android/matrix-android-sdk2/ - [ ] Build and run the sample, you may have to fix some API break - [ ] Commit and push directly on `main` validations: diff --git a/.github/dependabot.yml b/.github/dependabot.yml index 0573461e7a..b6746c77d3 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -10,6 +10,8 @@ updates: directory: "/" schedule: interval: "weekly" + reviewers: + - "vector-im/element-android-reviewers" ignore: - dependency-name: "*github-script*" # Updates for Gradle dependencies used in the app @@ -19,6 +21,6 @@ updates: interval: "daily" open-pull-requests-limit: 200 reviewers: - - "bmarty" + - "vector-im/element-android-reviewers" ignore: - dependency-name: com.google.zxing:core diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 22b3a1727d..9517a4f3a7 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -67,4 +67,4 @@ jobs: path: | vector/build/outputs/apk/*/release/*.apk -# TODO: add exodus checks +# TODO add exodus checks diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml new file mode 100644 index 0000000000..b6333c5940 --- /dev/null +++ b/.github/workflows/docs.yml @@ -0,0 +1,22 @@ +name: Documentation + +on: + push: + branches: [ develop ] + +jobs: + docs: + name: Generate and publish Android Matrix SDK documentation + runs-on: ubuntu-latest + steps: + - name: Checkout repository + uses: actions/checkout@v2 + + - name: Build docs + run: ./gradlew dokkaHtml + + - name: Deploy docs + uses: peaceiris/actions-gh-pages@v3 + with: + github_token: ${{ secrets.GITHUB_TOKEN }} + publish_dir: ./matrix-sdk-android/build/dokka/html diff --git a/.github/workflows/nightly.yml b/.github/workflows/post-pr.yml similarity index 83% rename from .github/workflows/nightly.yml rename to .github/workflows/post-pr.yml index 84122095c6..9cd33143ad 100644 --- a/.github/workflows/nightly.yml +++ b/.github/workflows/post-pr.yml @@ -1,28 +1,43 @@ -name: Nightly Tests +name: Integration Tests + +# This runs for all closed pull requests against main, including those closed without merge. +# Further filtering occurs in 'should-i-run' on: - push: - branches: [ release/* ] - schedule: - # At 20:00 every day UTC - - cron: '0 20 * * *' - workflow_dispatch: + pull_request: + types: [closed] + branches: [develop] # Enrich gradle.properties for CI/CD env: CI_GRADLE_ARG_PROPERTIES: > -Porg.gradle.jvmargs=-Xmx4g -Porg.gradle.parallel=false + jobs: + + # More info on should-i-run: + # If this fails to run (the IF doesn't complete) then the needs will not be satisfied for any of the + # other jobs below, so none will run. + # except for the notification job at the bottom which will run all the time, unless should-i-run isn't + # successful, or all the other jobs have succeeded + + should-i-run: + name: Check if PR is suitable for analysis + runs-on: ubuntu-latest + if: github.event.pull_request.merged # Additionally require PR to have been completely merged. + steps: + - run: echo "Run those tests!" # no-op success + # Run Android Tests integration-tests: name: Matrix SDK - Running Integration Tests + needs: should-i-run runs-on: macos-latest strategy: fail-fast: false matrix: api-level: [ 28 ] - # No concurrency required, runs every time on a schedule. steps: - uses: actions/checkout@v3 - uses: gradle/wrapper-validation-action@v1 @@ -43,11 +58,12 @@ jobs: restore-keys: | ${{ runner.os }}-gradle- - name: Start synapse server - uses: michaelkaye/setup-matrix-synapse@v1.0.1 + uses: michaelkaye/setup-matrix-synapse@v1.0.3 with: uploadLogs: true httpPort: 8080 disableRateLimiting: true + public_baseurl: "http://10.0.2.2:8080/" # package: org.matrix.android.sdk.session - name: Run integration tests for Matrix SDK [org.matrix.android.sdk.session] API[${{ matrix.api-level }}] uses: reactivecircus/android-emulator-runner@v2 @@ -209,6 +225,7 @@ jobs: ui-tests: name: UI Tests (Synapse) + needs: should-i-run runs-on: macos-latest strategy: fail-fast: false @@ -230,11 +247,12 @@ jobs: restore-keys: | ${{ runner.os }}-gradle- - name: Start synapse server - uses: michaelkaye/setup-matrix-synapse@v1.0.1 + uses: michaelkaye/setup-matrix-synapse@v1.0.3 with: uploadLogs: true httpPort: 8080 disableRateLimiting: true + public_baseurl: "http://10.0.2.2:8080/" - uses: actions/setup-java@v3 with: distribution: 'adopt' @@ -266,7 +284,8 @@ jobs: codecov-units: name: Unit tests with code coverage - runs-on: macos-latest + needs: should-i-run + runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 - uses: actions/setup-java@v3 @@ -290,50 +309,21 @@ jobs: path: | build/reports/jacoco/allCodeCoverageReport/allCodeCoverageReport.xml - sonarqube: - name: Sonarqube upload - runs-on: macos-latest - if: always() && github.event_name == 'schedule' - needs: - - codecov-units - steps: - - uses: actions/checkout@v3 - - uses: actions/setup-java@v3 - with: - distribution: 'adopt' - java-version: '11' - - uses: actions/cache@v3 - with: - path: | - ~/.gradle/caches - ~/.gradle/wrapper - key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle*', '**/gradle-wrapper.properties') }} - restore-keys: | - ${{ runner.os }}-gradle- - - uses: actions/download-artifact@v3 - with: - name: codecov-xml # will restore to allCodeCoverageReport.xml by default; we restore to the same location in following tasks - - run: mkdir -p build/reports/jacoco/allCodeCoverageReport/ - - run: mv allCodeCoverageReport.xml build/reports/jacoco/allCodeCoverageReport/ - - run: ./gradlew sonarqube $CI_GRADLE_ARG_PROPERTIES - env: - ORG_GRADLE_PROJECT_SONAR_LOGIN: ${{ secrets.SONAR_TOKEN }} - -# Notify the channel about scheduled runs, or pushes to the release branches, do not notify for manually triggered runs +# Notify the channel about delayed failures notify: name: Notify matrix runs-on: ubuntu-latest needs: + - should-i-run - integration-tests - ui-tests - - sonarqube - if: always() && github.event_name != 'workflow_dispatch' + - codecov-units + if: always() && (needs.should-i-run.result == 'success' ) && ((needs.codecov-units.result != 'success' ) || (needs.ui-tests.result != 'success') || (needs.integration-tests.result != 'success')) # No concurrency required, runs every time on a schedule. steps: - - uses: michaelkaye/matrix-hookshot-action@v0.3.0 + - uses: michaelkaye/matrix-hookshot-action@v1.0.0 with: github_token: ${{ secrets.GITHUB_TOKEN }} - matrix_access_token: ${{ secrets.ELEMENT_ANDROID_NOTIFICATION_ACCESS_TOKEN }} - matrix_room_id: ${{ secrets.ELEMENT_ANDROID_INTERNAL_ROOM_ID }} - text_template: "{{#if '${{ github.event_name }}' == 'schedule' }}Nightly test run{{else}}Test run (on ${{ github.ref }}){{/if }}: {{#each job_statuses }}{{#with this }}{{#if completed }} {{name}} {{conclusion}} at {{completed_at}}, {{/if}}{{/with}}{{/each}}" - html_template: "{{#if '${{ github.event_name }}' == 'schedule' }}Nightly test run{{else}}Test run (on ${{ github.ref }}){{/if }}: {{#each job_statuses }}{{#with this }}{{#if completed }}
{{icon conclusion}} {{name}} {{conclusion}} at {{completed_at}} [details]{{/if}}{{/with}}{{/each}}" + hookshot_url: ${{ secrets.ELEMENT_ANDROID_HOOKSHOT_URL }} + text_template: "Post-merge validation of ${{ github.head_ref }} into ${{ github.base_ref }} by ${{ github.event.pull_request.merged_by }} failed: {{#each job_statuses }}{{#with this }}{{#if completed }} {{name}} {{conclusion}} at {{completed_at}}, {{/if}}{{/with}}{{/each}}" + html_template: "Post-merge validation of ${{ github.head_ref }} into ${{ github.base_ref }} by ${{ github.event.pull_request.merged_by }} failed: {{#each job_statuses }}{{#with this }}{{#if completed }}
{{icon conclusion}} {{name}} {{conclusion}} at {{completed_at}} [details]{{/if}}{{/with}}{{/each}}" diff --git a/.github/workflows/quality.yml b/.github/workflows/quality.yml index dee596980f..a97d532644 100644 --- a/.github/workflows/quality.yml +++ b/.github/workflows/quality.yml @@ -14,6 +14,16 @@ jobs: - name: Run code quality check suite run: ./tools/check/check_code_quality.sh +# Knit for all the modules (https://github.com/Kotlin/kotlinx-knit) + knit: + name: Knit + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + - name: Run knit + run: | + ./gradlew knit + # ktlint for all the modules ktlint: name: Kotlin Linter @@ -147,3 +157,23 @@ jobs: name: release-lint-report-${{ matrix.target }} path: | vector/build/reports/*.* + + detekt: + name: Detekt Analysis + runs-on: ubuntu-latest + # Allow all jobs on main and develop. Just one per PR. + concurrency: + group: ${{ github.ref == 'refs/heads/main' && format('detekt-main-{0}', github.sha) || github.ref == 'refs/heads/develop' && format('detekt-develop-{0}', github.sha) || format('detekt-{0}', github.ref) }} + cancel-in-progress: true + steps: + - uses: actions/checkout@v3 + - name: Run detekt + run: | + ./gradlew detekt + - name: Upload reports + if: always() + uses: actions/upload-artifact@v3 + with: + name: detekt-report + path: | + */build/reports/detekt/detekt.html diff --git a/.github/workflows/sonarqube.yml b/.github/workflows/sonarqube.yml new file mode 100644 index 0000000000..6809751d91 --- /dev/null +++ b/.github/workflows/sonarqube.yml @@ -0,0 +1,81 @@ +name: Sonarqube nightly + +on: + schedule: + - cron: '0 20 * * *' + +# Enrich gradle.properties for CI/CD +env: + CI_GRADLE_ARG_PROPERTIES: > + -Porg.gradle.jvmargs=-Xmx4g + -Porg.gradle.parallel=false +jobs: + codecov-units: + name: Unit tests with code coverage + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + - uses: actions/setup-java@v3 + with: + distribution: 'adopt' + java-version: '11' + - uses: actions/cache@v3 + with: + path: | + ~/.gradle/caches + ~/.gradle/wrapper + key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle*', '**/gradle-wrapper.properties') }} + restore-keys: | + ${{ runner.os }}-gradle- + - run: ./gradlew allCodeCoverageReport $CI_GRADLE_ARG_PROPERTIES + - name: Upload Codecov data + uses: actions/upload-artifact@v3 + if: always() + with: + name: codecov-xml + path: | + build/reports/jacoco/allCodeCoverageReport/allCodeCoverageReport.xml + + sonarqube: + name: Sonarqube upload + runs-on: ubuntu-latest + needs: + - codecov-units + steps: + - uses: actions/checkout@v3 + - uses: actions/setup-java@v3 + with: + distribution: 'adopt' + java-version: '11' + - uses: actions/cache@v3 + with: + path: | + ~/.gradle/caches + ~/.gradle/wrapper + key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle*', '**/gradle-wrapper.properties') }} + restore-keys: | + ${{ runner.os }}-gradle- + - uses: actions/download-artifact@v3 + with: + name: codecov-xml # will restore to allCodeCoverageReport.xml by default; we restore to the same location in following tasks + - run: mkdir -p build/reports/jacoco/allCodeCoverageReport/ + - run: mv allCodeCoverageReport.xml build/reports/jacoco/allCodeCoverageReport/ + - run: ./gradlew sonarqube $CI_GRADLE_ARG_PROPERTIES + env: + ORG_GRADLE_PROJECT_SONAR_LOGIN: ${{ secrets.SONAR_TOKEN }} + +# Notify the channel about sonarqube failures + notify: + name: Notify matrix + runs-on: ubuntu-latest + needs: + - sonarqube + - codecov-units + if: always() && (needs.sonarqube.result != 'success' || needs.codecov-units.result != 'success') + steps: + - uses: michaelkaye/matrix-hookshot-action@v1.0.0 + with: + github_token: ${{ secrets.GITHUB_TOKEN }} + hookshot_url: ${{ secrets.ELEMENT_ANDROID_HOOKSHOT_URL }} + text_template: "Sonarqube run (on ${{ github.ref }}): {{#each job_statuses }}{{#with this }}{{#if completed }} {{name}} {{conclusion}} at {{completed_at}}, {{/if}}{{/with}}{{/each}}" + html_template: "Sonarqube run (on ${{ github.ref }}): {{#each job_statuses }}{{#with this }}{{#if completed }}
{{icon conclusion}} {{name}} {{conclusion}} at {{completed_at}} [details]{{/if}}{{/with}}{{/each}}" diff --git a/.github/workflows/triage-labelled.yml b/.github/workflows/triage-labelled.yml index eeddf2e785..82d5931ce7 100644 --- a/.github/workflows/triage-labelled.yml +++ b/.github/workflows/triage-labelled.yml @@ -11,7 +11,6 @@ jobs: if: > contains(github.event.issue.labels.*.name, 'A-Maths') || contains(github.event.issue.labels.*.name, 'A-Message-Pinning') || - contains(github.event.issue.labels.*.name, 'A-Threads') || contains(github.event.issue.labels.*.name, 'A-Polls') || contains(github.event.issue.labels.*.name, 'A-Location-Sharing') || contains(github.event.issue.labels.*.name, 'A-Message-Bubbles') || @@ -252,3 +251,30 @@ jobs: env: PROJECT_ID: "PN_kwDOAM0swc4AArk0" GITHUB_TOKEN: ${{ secrets.ELEMENT_BOT_TOKEN }} + + move_element_x_issues: + name: ElementX issues to ElementX project board + runs-on: ubuntu-latest + # Skip in forks + if: > + github.repository == 'vector-im/element-android' && + (contains(github.event.issue.labels.*.name, 'Z-ElementX-Alpha') || + contains(github.event.issue.labels.*.name, 'Z-ElementX-Beta') || + contains(github.event.issue.labels.*.name, 'Z-ElementX')) + steps: + - uses: octokit/graphql-action@v2.x + with: + headers: '{"GraphQL-Features": "projects_next_graphql"}' + query: | + mutation add_to_project($projectid:ID!,$contentid:ID!) { + addProjectNextItem(input:{projectId:$projectid contentId:$contentid}) { + projectNextItem { + id + } + } + } + projectid: ${{ env.PROJECT_ID }} + contentid: ${{ github.event.issue.node_id }} + env: + PROJECT_ID: "PN_kwDOAM0swc4ABTXY" + GITHUB_TOKEN: ${{ secrets.ELEMENT_BOT_TOKEN }} diff --git a/.github/workflows/triage-move-review-requests.yml b/.github/workflows/triage-move-review-requests.yml index 75738a53a9..61f1f114dd 100644 --- a/.github/workflows/triage-move-review-requests.yml +++ b/.github/workflows/triage-move-review-requests.yml @@ -7,6 +7,8 @@ jobs: add_design_pr_to_project: name: Move PRs asking for design review to the design board runs-on: ubuntu-latest + # Skip in forks + if: github.repository == 'vector-im/element-android' steps: - uses: octokit/graphql-action@v2.x id: find_team_members @@ -74,6 +76,8 @@ jobs: add_product_pr_to_project: name: Move PRs asking for product review to the product board runs-on: ubuntu-latest + # Skip in forks + if: github.repository == 'vector-im/element-android' steps: - uses: octokit/graphql-action@v2.x id: find_team_members diff --git a/.github/workflows/update-gradle-wrapper.yml b/.github/workflows/update-gradle-wrapper.yml index 1cbf29cc8d..63aaae15a5 100644 --- a/.github/workflows/update-gradle-wrapper.yml +++ b/.github/workflows/update-gradle-wrapper.yml @@ -13,6 +13,8 @@ jobs: - name: Update Gradle Wrapper uses: gradle-update/update-gradle-wrapper-action@v1 + # Skip in forks + if: github.repository == 'vector-im/element-android' with: repo-token: ${{ secrets.GITHUB_TOKEN }} target-branch: develop diff --git a/CHANGES.md b/CHANGES.md index 47b8c57041..c8677b1ae4 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,3 +1,169 @@ +Changes in Element v1.4.16 (2022-05-17) +======================================= + +Features ✨ +---------- + - Use key backup before requesting keys + refactor & improvement of key request/forward ([#5494](https://github.com/vector-im/element-android/issues/5494)) + - Screen sharing over WebRTC ([#5911](https://github.com/vector-im/element-android/issues/5911)) + - Allow using the latest user Avatar and name for all messages in the timeline ([#5932](https://github.com/vector-im/element-android/issues/5932)) + - Added themed launch icons for Android 13 ([#5936](https://github.com/vector-im/element-android/issues/5936)) + - Add presence indicator busy and away. ([#6047](https://github.com/vector-im/element-android/issues/6047)) + +Bugfixes 🐛 +---------- + - Changed copy and list order in member profile screen. ([#5825](https://github.com/vector-im/element-android/issues/5825)) + - Fix for audio only being received in one direction after an un-hold during a sip call. ([#5865](https://github.com/vector-im/element-android/issues/5865)) + - Desynchronized 4S | Megolm backup causing Unusable backup ([#5906](https://github.com/vector-im/element-android/issues/5906)) + - If animations are disable on the System, chat effects and confetti will be disabled too ([#5941](https://github.com/vector-im/element-android/issues/5941)) + - Multiple threads improvement (mainly UI) ([#5959](https://github.com/vector-im/element-android/issues/5959)) + +Improved Documentation 📚 +------------------------ + - Note public_baseurl requirement in integration tests documentation. ([#5973](https://github.com/vector-im/element-android/issues/5973)) + +SDK API changes ⚠️ +------------------ + - - New API to enable/disable key forwarding CryptoService#enableKeyGossiping() + - New API to limit room key request only to own devices MXCryptoConfig#limitRoomKeyRequestsToMyDevices + - Event Trail API has changed, now using AuditTrail events + - New API to manually accept an incoming key request CryptoService#manuallyAcceptRoomKeyRequest() ([#5559](https://github.com/vector-im/element-android/issues/5559)) + - Small change in the Matrix class: deprecated methods have been removed and the constructor is now public. Also the fun `workerFactory()` has been renamed to `getWorkerFactory()` ([#5887](https://github.com/vector-im/element-android/issues/5887)) + - Including SSL/TLS error handing when doing WellKnown lookups without a custom HomeServerConnectionConfig ([#5965](https://github.com/vector-im/element-android/issues/5965)) + +Other changes +------------- + - Improve threads rendering in the main timeline ([#5151](https://github.com/vector-im/element-android/issues/5151)) + - Reformatted project code ([#5953](https://github.com/vector-im/element-android/issues/5953)) + - Update check for server-side threads support to match spec. ([#5997](https://github.com/vector-im/element-android/issues/5997)) + - Setup detekt ([#6038](https://github.com/vector-im/element-android/issues/6038)) + - Notify the user for each new message ([#46312](https://github.com/vector-im/element-android/issues/46312)) + + +Changes in Element v1.4.14 (2022-05-05) +======================================= + +Features ✨ +---------- + - Improve management of ignored users ([#5772](https://github.com/vector-im/element-android/issues/5772)) + - VoIP Screen Sharing Permission ([#5811](https://github.com/vector-im/element-android/issues/5811)) + - Live location sharing: updating beacon state event content structure ([#5814](https://github.com/vector-im/element-android/issues/5814)) + +Bugfixes 🐛 +---------- + - Fixes crash when accepting or receiving VOIP calls ([#5421](https://github.com/vector-im/element-android/issues/5421)) + - Improve/fix crashes on messages decryption ([#5592](https://github.com/vector-im/element-android/issues/5592)) + - Tentative fix of images crashing when being sent or shared from gallery ([#5652](https://github.com/vector-im/element-android/issues/5652)) + - Improving deactivation experience along with a crash fix ([#5721](https://github.com/vector-im/element-android/issues/5721)) + - Adds missing suggested tag for rooms in Explore Space ([#5826](https://github.com/vector-im/element-android/issues/5826)) + - Fixes missing call icons when threads are enabled ([#5847](https://github.com/vector-im/element-android/issues/5847)) + - Fix UX freezing when creating secure backup ([#5871](https://github.com/vector-im/element-android/issues/5871)) + - Fixes sign in via other requiring homeserver registration to be enabled ([#5874](https://github.com/vector-im/element-android/issues/5874)) + - Don't pause timer when call is held. ([#5885](https://github.com/vector-im/element-android/issues/5885)) + - Fix UISIDetector grace period bug ([#5886](https://github.com/vector-im/element-android/issues/5886)) + - Fix a crash with space invitations in the space list, and do not display space invitation twice. ([#5924](https://github.com/vector-im/element-android/issues/5924)) + - Fixes crash on android api 21/22 devices when opening messages due to Konfetti library ([#5925](https://github.com/vector-im/element-android/issues/5925)) + +In development 🚧 +---------------- + - Reorders the registration steps to prioritise email, then terms for the FTUE onboarding ([#5783](https://github.com/vector-im/element-android/issues/5783)) + - [Live location sharing] Improve aggregation process of events ([#5862](https://github.com/vector-im/element-android/issues/5862)) + +Improved Documentation 📚 +------------------------ + - Update the PR process doc with 2 reviewers and a new reviewer team. ([#5836](https://github.com/vector-im/element-android/issues/5836)) + - Improve documentation of the project and of the SDK ([#5854](https://github.com/vector-im/element-android/issues/5854)) + +SDK API changes ⚠️ +------------------ + - Added registrationCustom into RegistrationWizard to send custom auth params for sign up + - Moved terms converter into api package to make it accessible in sdk ([#5575](https://github.com/vector-im/element-android/issues/5575)) + - Move package `org.matrix.android.sdk.api.pushrules` to `org.matrix.android.sdk.api.session.pushrules` ([#5812](https://github.com/vector-im/element-android/issues/5812)) + - Some `Session` apis are now available by requesting the service first. For instance `Session.updateAvatar(...)` is now `Session.profileService().updateAvatar(...)` + - The shortcut `Room.search()` has been removed, you have to use `Session.searchService().search()` ([#5816](https://github.com/vector-im/element-android/issues/5816)) + - Add return type to RoomApi.sendStateEvent() to retrieve the created event id ([#5855](https://github.com/vector-im/element-android/issues/5855)) + - `Room` apis are now available by requesting the service first. For instance `Room.updateAvatar(...)` is now `Room.stateService().updateAvatar(...)` ([#5858](https://github.com/vector-im/element-android/issues/5858)) + - Remove unecessary field `eventId` from `EventAnnotationsSummary` and `ReferencesAggregatedSummary` ([#5890](https://github.com/vector-im/element-android/issues/5890)) + - Replace usage of `System.currentTimeMillis()` by a `Clock` interface ([#5907](https://github.com/vector-im/element-android/issues/5907)) + +Other changes +------------- + - Move "Ignored users" setting section into "Security & Privacy" ([#5773](https://github.com/vector-im/element-android/issues/5773)) + - Add a picto for ignored users in the room member list screen ([#5774](https://github.com/vector-im/element-android/issues/5774)) + - Autoformats entire project ([#5805](https://github.com/vector-im/element-android/issues/5805)) + - Add a GH workflow to push ElementX issues to the global board. ([#5832](https://github.com/vector-im/element-android/issues/5832)) + - Faster Olm decrypt when there is a lot of existing sessions ([#5872](https://github.com/vector-im/element-android/issues/5872)) + + +Changes in Element 1.4.13 (2022-04-26) +====================================== + +Bugfixes 🐛 +---------- + - Fix UI freeze observed after each incremental sync ([#5835](https://github.com/vector-im/element-android/issues/5835)) + + +Changes in Element v1.4.12 (2022-04-20) +======================================= + +Features ✨ +---------- + - Add a setting to be able to always appear offline ([#5582](https://github.com/vector-im/element-android/issues/5582)) + - Adds the ability for audio attachments to be played in the timeline ([#5586](https://github.com/vector-im/element-android/issues/5586)) + - Do not cancel the current incremental sync request and treatment when the app goes to background ([#5719](https://github.com/vector-im/element-android/issues/5719)) + - Improve user experience when home servers do not yet support threads ([#5761](https://github.com/vector-im/element-android/issues/5761)) + +Bugfixes 🐛 +---------- + - Added text next to spinner when loading information after user is clicked on space members screen ([#4305](https://github.com/vector-im/element-android/issues/4305)) + - The string `ftue_auth_carousel_workplace_body` was declared not translatable by mistake ([#5262](https://github.com/vector-im/element-android/issues/5262)) + - Fix some cases where the read marker line would not show up ([#5475](https://github.com/vector-im/element-android/issues/5475)) + - Fix sometimes read marker not properly updating ([#5481](https://github.com/vector-im/element-android/issues/5481)) + - Fix sometimes endless loading timeline ([#5554](https://github.com/vector-im/element-android/issues/5554)) + - Use member name instead of room name in DM creation item ([#5570](https://github.com/vector-im/element-android/issues/5570)) + - Align auto-reporting of decryption errors implementation with web client. ([#5596](https://github.com/vector-im/element-android/issues/5596)) + - Choosing "leave all rooms and spaces" while leaving Space won't cause leaving DMs in this Space anymore ([#5609](https://github.com/vector-im/element-android/issues/5609)) + - Fixes display name being changed when using /myroomnick ([#5618](https://github.com/vector-im/element-android/issues/5618)) + - Fix endless loading if the event from a permalink is not found ([#5659](https://github.com/vector-im/element-android/issues/5659)) + - Redacted events are no longer visible. ([#5707](https://github.com/vector-im/element-android/issues/5707)) + - Don't wrongly show non-space invites in the space panel. ([#5731](https://github.com/vector-im/element-android/issues/5731)) + - Fixes the onboarding confetti rendering behind the content instead of in-front ([#5735](https://github.com/vector-im/element-android/issues/5735)) + - Fixes crash when navigating the app whilst processing new room keys ([#5746](https://github.com/vector-im/element-android/issues/5746)) + - Fix sorting of uploads in encrypted rooms ([#5757](https://github.com/vector-im/element-android/issues/5757)) + - Fixing setting transfer title in call transfer. ([#5765](https://github.com/vector-im/element-android/issues/5765)) + - Changes destination after joining a space to Explore Space Rooms screen ([#5766](https://github.com/vector-im/element-android/issues/5766)) + - Unignoring a user will perform an initial sync ([#5767](https://github.com/vector-im/element-android/issues/5767)) + - Open a room by link: use the actual roomId instead of the alias ([#5786](https://github.com/vector-im/element-android/issues/5786)) + +In development 🚧 +---------------- + - FTUE - Adds a new homeserver selection screen when creating an account ([#2396](https://github.com/vector-im/element-android/issues/2396)) + - FTUE - Updates the Captcha and T&Cs registration screens UI style ([#5279](https://github.com/vector-im/element-android/issues/5279)) + - FTUE - Adds error handling within the server selection screen ([#5749](https://github.com/vector-im/element-android/issues/5749)) + - Live Location Sharing - Send location data ([#5697](https://github.com/vector-im/element-android/issues/5697)) + - Live Location Sharing - Show message on start of a live ([#5710](https://github.com/vector-im/element-android/issues/5710)) + - Live Location Sharing - Attach location data to beacon info state event ([#5711](https://github.com/vector-im/element-android/issues/5711)) + - Live Location Sharing - Update beacon info state event when sharing is ended ([#5758](https://github.com/vector-im/element-android/issues/5758)) + + +SDK API changes ⚠️ +------------------ + - Include original event in live decryption listeners and update sync status naming to InitialSyncProgressing for clarity. ([#5639](https://github.com/vector-im/element-android/issues/5639)) + - KeysBackupService.getCurrentVersion takes a new type `KeysBackupLastVersionResult` in the callback. ([#5703](https://github.com/vector-im/element-android/issues/5703)) + - A lot of classes which were exposed to the clients and were located in the package `org.matrix.android.sdk.internal` have been moved to the package `org.matrix.android.sdk.api`. + All the classes which are in the package `org.matrix.android.sdk.internal` should now be declared `internal`. + Some unused code and classes have been removed. ([#5744](https://github.com/vector-im/element-android/issues/5744)) + - Some data classes are now immutable, using `val` instead of `var` ([#5762](https://github.com/vector-im/element-android/issues/5762)) + +Other changes +------------- + - Upgrade konfetti lib from 1.3.2 to 2.0.2 ([#5079](https://github.com/vector-im/element-android/issues/5079)) + - Spaces feedback section is removed from left panel ([#5486](https://github.com/vector-im/element-android/issues/5486)) + - Reduce error logs ([#5703](https://github.com/vector-im/element-android/issues/5703)) + - Adds a complete editor config file for our current code style ([#5727](https://github.com/vector-im/element-android/issues/5727)) + - Updates the posthog dev environment url and api key ([#5732](https://github.com/vector-im/element-android/issues/5732)) + - Setup Dokka to be able to generate documentation of the SDK module. Run `./gradlew matrix-sdk-android:dokkaHtml` to do it. ([#5744](https://github.com/vector-im/element-android/issues/5744)) + + Changes in Element v1.4.11 (2022-04-07) ======================================= diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 053931cac5..52ccf47e6a 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -124,7 +124,9 @@ As a general rule, please never edit or add or remove translations to the projec #### Adding new string -When adding new string resources, please only add new entries in file `value/strings.xml`. Translations will be added later by the community of translators using Weblate. +When adding new string resources, please only add new entries in the file `value/strings.xml`. Translations will be added later by the community of translators using Weblate. + +The file `value/strings.xml` must only contain American English (U. S. English) values, as this is the default language of the Android operating system. So for instance, please use "color" instead of "colour". Element Android will still use the language set on the system by the user, like any other Android applications which provide translations. The system language can be any other English language variants, or any other languages. Note that this is also possible to override the system language using the Element Android in-app language settings. New strings can be added anywhere in the file `value/strings.xml`, not necessarily at the end of the file. Generally, it's even better to add the new strings in some dedicated section per feature, and not at the end of the file, to avoid merge conflict between 2 PR adding strings at the end of the same file. diff --git a/README.md b/README.md index 8306fd8593..54dfb7b288 100644 --- a/README.md +++ b/README.md @@ -14,7 +14,7 @@ It is a total rewrite of [Riot-Android](https://github.com/vector-im/riot-androi [Get it on Google Play](https://play.google.com/store/apps/details?id=im.vector.app) [Get it on F-Droid](https://f-droid.org/app/im.vector.app) -Nightly build: [![Buildkite](https://badge.buildkite.com/ad0065c1b70f557cd3b1d3d68f9c2154010f83c4d6f71706a9.svg?branch=develop)](https://buildkite.com/matrix-dot-org/element-android/builds?branch=develop) Nighly test status: [![allScreensTest](https://github.com/vector-im/element-android/actions/workflows/nightly.yml/badge.svg)](https://github.com/vector-im/element-android/actions/workflows/nightly.yml) +Nightly build: [![Buildkite](https://badge.buildkite.com/ad0065c1b70f557cd3b1d3d68f9c2154010f83c4d6f71706a9.svg?branch=develop)](https://buildkite.com/matrix-dot-org/element-android/builds?branch=develop) Nightly test status: [![allScreensTest](https://github.com/vector-im/element-android/actions/workflows/nightly.yml/badge.svg)](https://github.com/vector-im/element-android/actions/workflows/nightly.yml) # New Android SDK @@ -53,3 +53,4 @@ Come chat with the community in the dedicated Matrix [room](https://matrix.to/#/ Issues are triaged by community members and the Android App Team, following the [triage process](https://github.com/vector-im/element-meta/wiki/Triage-process). We use [issue labels](https://github.com/vector-im/element-meta/wiki/Issue-labelling) to sort all incoming issues. + diff --git a/build.gradle b/build.gradle index 6cbc11351e..cf49f37809 100644 --- a/build.gradle +++ b/build.gradle @@ -5,10 +5,17 @@ buildscript { apply from: 'dependencies_groups.gradle' repositories { - google() + // Do not use `google()`, it prevents Dependabot from working properly + maven { + url 'https://maven.google.com' + } maven { url "https://plugins.gradle.org/m2/" } + // Do not use `mavenCentral()`, it prevents Dependabot from working properly + maven { + url 'https://repo1.maven.org/maven2' + } } dependencies { @@ -21,16 +28,19 @@ buildscript { classpath 'org.sonarsource.scanner.gradle:sonarqube-gradle-plugin:3.3' classpath 'com.google.android.gms:oss-licenses-plugin:0.10.5' classpath "com.likethesalad.android:stem-plugin:2.0.0" - classpath 'org.owasp:dependency-check-gradle:7.0.4.1' - classpath "org.jetbrains.dokka:dokka-gradle-plugin:1.6.20" + classpath 'org.owasp:dependency-check-gradle:7.1.0.1' + classpath "org.jetbrains.dokka:dokka-gradle-plugin:1.6.21" + classpath "org.jetbrains.kotlinx:kotlinx-knit:0.4.0" // NOTE: Do not place your application dependencies here; they belong // in the individual module build.gradle files } } -// ktlint Plugin plugins { - id "org.jlleitschuh.gradle.ktlint" version "10.2.1" + // ktlint Plugin + id "org.jlleitschuh.gradle.ktlint" version "10.3.0" + // Detekt + id "io.gitlab.arturbosch.detekt" version "1.20.0" id 'com.autonomousapps.dependency-analysis' version "1.0.0" } @@ -46,9 +56,12 @@ dependencyCheck { allprojects { apply plugin: "org.jlleitschuh.gradle.ktlint" + apply plugin: "io.gitlab.arturbosch.detekt" repositories { - mavenCentral { + // Do not use `mavenCentral()`, it prevents Dependabot from working properly + maven { + url 'https://repo1.maven.org/maven2' content { groups.mavenCentral.regex.each { includeGroupByRegex it } groups.mavenCentral.group.each { includeGroup it } @@ -71,14 +84,18 @@ allprojects { groups.jitsi.group.each { includeGroup it } } } - google { + // Do not use `google()`, it prevents Dependabot from working properly + maven { + url 'https://maven.google.com' content { groups.google.regex.each { includeGroupByRegex it } groups.google.group.each { includeGroup it } } } //noinspection JcenterRepositoryObsolete - jcenter { + // Do not use `jcenter`, it prevents Dependabot from working properly + maven { + url 'https://jcenter.bintray.com' content { groups.jcenter.regex.each { includeGroupByRegex it } groups.jcenter.group.each { includeGroup it } @@ -107,7 +124,7 @@ allprojects { // display the corresponding rule verbose = true disabledRules = [ - // TODO: Re-enable these 4 rules after reformatting project + // TODO Re-enable these 4 rules after reformatting project "indent", "experimental:argument-list-wrapping", "max-line-length", @@ -128,6 +145,15 @@ allprojects { "experimental:kdoc-wrapping", ] } + + detekt { + // preconfigure defaults + buildUponDefaultConfig = true + // activate all available (even unstable) rules. + allRules = true + // point to your custom config defining rules to run, overwriting default behavior + config = files("$rootDir/tools/detekt/detekt.yml") + } } task clean(type: Delete) { diff --git a/changelog.d/2396.wip b/changelog.d/2396.wip deleted file mode 100644 index 9af8fa16c0..0000000000 --- a/changelog.d/2396.wip +++ /dev/null @@ -1 +0,0 @@ -Adds a new homeserver selection screen when creating an account \ No newline at end of file diff --git a/changelog.d/4305.bugfix b/changelog.d/4305.bugfix deleted file mode 100644 index 95e4a966fc..0000000000 --- a/changelog.d/4305.bugfix +++ /dev/null @@ -1 +0,0 @@ -Added text next to spinner when loading information after user is clicked on space members screen \ No newline at end of file diff --git a/changelog.d/5079.misc b/changelog.d/5079.misc deleted file mode 100644 index c38bf043ea..0000000000 --- a/changelog.d/5079.misc +++ /dev/null @@ -1 +0,0 @@ -Upgrade konfetti lib from 1.3.2 to 2.0.2 \ No newline at end of file diff --git a/changelog.d/5262.bugfix b/changelog.d/5262.bugfix deleted file mode 100644 index 389c1ed8fa..0000000000 --- a/changelog.d/5262.bugfix +++ /dev/null @@ -1 +0,0 @@ -The string `ftue_auth_carousel_workplace_body` was declared not translatable by mistake \ No newline at end of file diff --git a/changelog.d/5279.wip b/changelog.d/5279.wip deleted file mode 100644 index 0c08081cf9..0000000000 --- a/changelog.d/5279.wip +++ /dev/null @@ -1 +0,0 @@ -Updates the Captcha and T&Cs registration screens in the FTUE flow to match the updated UI style \ No newline at end of file diff --git a/changelog.d/5475.bugfix b/changelog.d/5475.bugfix deleted file mode 100644 index 03364f6a73..0000000000 --- a/changelog.d/5475.bugfix +++ /dev/null @@ -1 +0,0 @@ -Fix some cases where the read marker line would not show up diff --git a/changelog.d/5481.bugfix b/changelog.d/5481.bugfix deleted file mode 100644 index 64891b503c..0000000000 --- a/changelog.d/5481.bugfix +++ /dev/null @@ -1 +0,0 @@ -Fix sometimes read marker not properly updating diff --git a/changelog.d/5554.bugfix b/changelog.d/5554.bugfix deleted file mode 100644 index ee69f0dbfe..0000000000 --- a/changelog.d/5554.bugfix +++ /dev/null @@ -1 +0,0 @@ -Fix sometimes endless loading timeline diff --git a/changelog.d/5570.bugfix b/changelog.d/5570.bugfix deleted file mode 100644 index c3743d252f..0000000000 --- a/changelog.d/5570.bugfix +++ /dev/null @@ -1 +0,0 @@ -Use member name instead of room name in DM creation item \ No newline at end of file diff --git a/changelog.d/5582.feature b/changelog.d/5582.feature deleted file mode 100644 index e6e72c8e9d..0000000000 --- a/changelog.d/5582.feature +++ /dev/null @@ -1 +0,0 @@ -Add a setting to be able to always appear offline diff --git a/changelog.d/5586.feature b/changelog.d/5586.feature deleted file mode 100644 index 17d7bfce86..0000000000 --- a/changelog.d/5586.feature +++ /dev/null @@ -1 +0,0 @@ -Adds the ability for audio attachments to be played in the timeline diff --git a/changelog.d/5596.bugfix b/changelog.d/5596.bugfix deleted file mode 100644 index f51794c352..0000000000 --- a/changelog.d/5596.bugfix +++ /dev/null @@ -1 +0,0 @@ -Align auto-reporting of decryption errors implementation with web client. \ No newline at end of file diff --git a/changelog.d/5609.bugfix b/changelog.d/5609.bugfix deleted file mode 100644 index 001b4bf400..0000000000 --- a/changelog.d/5609.bugfix +++ /dev/null @@ -1 +0,0 @@ -Choosing "leave all rooms and spaces" while leaving Space won't cause leaving DMs in this Space anymore \ No newline at end of file diff --git a/changelog.d/5618.bugfix b/changelog.d/5618.bugfix deleted file mode 100644 index 8a839a2b4e..0000000000 --- a/changelog.d/5618.bugfix +++ /dev/null @@ -1 +0,0 @@ -Fixes display name being changed when using /myroomnick diff --git a/changelog.d/5639.sdk b/changelog.d/5639.sdk deleted file mode 100644 index 66167273f9..0000000000 --- a/changelog.d/5639.sdk +++ /dev/null @@ -1 +0,0 @@ -Include original event in live decryption listeners and update sync status naming to InitialSyncProgressing for clarity. \ No newline at end of file diff --git a/changelog.d/5659.bugfix b/changelog.d/5659.bugfix deleted file mode 100644 index eec39a7738..0000000000 --- a/changelog.d/5659.bugfix +++ /dev/null @@ -1 +0,0 @@ -Fix endless loading if the event from a permalink is not found diff --git a/changelog.d/5697.feature b/changelog.d/5697.feature deleted file mode 100644 index 47504084f4..0000000000 --- a/changelog.d/5697.feature +++ /dev/null @@ -1 +0,0 @@ -Live Location Sharing - Send location data \ No newline at end of file diff --git a/changelog.d/5703.misc b/changelog.d/5703.misc deleted file mode 100644 index 05e5080410..0000000000 --- a/changelog.d/5703.misc +++ /dev/null @@ -1 +0,0 @@ -Reduce error logs \ No newline at end of file diff --git a/changelog.d/5703.sdk b/changelog.d/5703.sdk deleted file mode 100644 index ece1fec8e8..0000000000 --- a/changelog.d/5703.sdk +++ /dev/null @@ -1 +0,0 @@ -KeysBackupService.getCurrentVersion takes a new type `KeysBackupLastVersionResult` in the callback. \ No newline at end of file diff --git a/changelog.d/5707.bugfix b/changelog.d/5707.bugfix deleted file mode 100644 index 66bd8f30b6..0000000000 --- a/changelog.d/5707.bugfix +++ /dev/null @@ -1 +0,0 @@ -Redacted events are no longer visible. \ No newline at end of file diff --git a/changelog.d/5710.feature b/changelog.d/5710.feature deleted file mode 100644 index d9b043bc32..0000000000 --- a/changelog.d/5710.feature +++ /dev/null @@ -1 +0,0 @@ -Live Location Sharing - Show message on start of a live \ No newline at end of file diff --git a/changelog.d/5711.feature b/changelog.d/5711.feature deleted file mode 100644 index 76c6b23b69..0000000000 --- a/changelog.d/5711.feature +++ /dev/null @@ -1 +0,0 @@ -Live Location Sharing - Attach location data to beacon info state event \ No newline at end of file diff --git a/changelog.d/5719.feature b/changelog.d/5719.feature deleted file mode 100644 index 7561dfd82f..0000000000 --- a/changelog.d/5719.feature +++ /dev/null @@ -1 +0,0 @@ -Do not cancel the current incremental sync request and treatment when the app goes to background \ No newline at end of file diff --git a/changelog.d/5727.misc b/changelog.d/5727.misc deleted file mode 100644 index fd90a08b17..0000000000 --- a/changelog.d/5727.misc +++ /dev/null @@ -1 +0,0 @@ -Adds a complete editor config file for our current code style diff --git a/changelog.d/5731.bugfix b/changelog.d/5731.bugfix deleted file mode 100644 index 5fb639b3ef..0000000000 --- a/changelog.d/5731.bugfix +++ /dev/null @@ -1 +0,0 @@ -Don't wrongly show non-space invites in the space panel. diff --git a/changelog.d/5732.misc b/changelog.d/5732.misc deleted file mode 100644 index 1b0f89e0c0..0000000000 --- a/changelog.d/5732.misc +++ /dev/null @@ -1 +0,0 @@ -Updates the posthog dev environment url and api key \ No newline at end of file diff --git a/changelog.d/5735.bugfix b/changelog.d/5735.bugfix deleted file mode 100644 index 99012f9254..0000000000 --- a/changelog.d/5735.bugfix +++ /dev/null @@ -1 +0,0 @@ -Fixes the onboarding confetti rendering behind the content instead of in-front \ No newline at end of file diff --git a/changelog.d/5744.misc b/changelog.d/5744.misc deleted file mode 100644 index 0953eccd95..0000000000 --- a/changelog.d/5744.misc +++ /dev/null @@ -1 +0,0 @@ -Setup Dokka to be able to generate documentation of the SDK module. Run `./gradlew matrix-sdk-android:dokkaHtml` to do it. \ No newline at end of file diff --git a/changelog.d/5744.sdk b/changelog.d/5744.sdk deleted file mode 100644 index aced939e55..0000000000 --- a/changelog.d/5744.sdk +++ /dev/null @@ -1,3 +0,0 @@ -A lot of classes which were exposed to the clients and were located in the package `org.matrix.android.sdk.internal` have been moved to the package `org.matrix.android.sdk.api`. -All the classes which are in the package `org.matrix.android.sdk.internal` should now be declared `internal`. -Some unused code and classes have been removed. \ No newline at end of file diff --git a/changelog.d/5746.bugfix b/changelog.d/5746.bugfix deleted file mode 100644 index 975a75f70a..0000000000 --- a/changelog.d/5746.bugfix +++ /dev/null @@ -1 +0,0 @@ -Fixes crash when navigating the app whilst processing new room keys \ No newline at end of file diff --git a/changelog.d/5749.wip b/changelog.d/5749.wip deleted file mode 100644 index a933f55cf5..0000000000 --- a/changelog.d/5749.wip +++ /dev/null @@ -1 +0,0 @@ -Adds error handling within the new FTUE server selection screen \ No newline at end of file diff --git a/changelog.d/5757.bugfix b/changelog.d/5757.bugfix deleted file mode 100644 index 089cc23b4c..0000000000 --- a/changelog.d/5757.bugfix +++ /dev/null @@ -1 +0,0 @@ -Fix sorting of uploads in encrypted rooms \ No newline at end of file diff --git a/changelog.d/5758.feature b/changelog.d/5758.feature deleted file mode 100644 index 512b5c3868..0000000000 --- a/changelog.d/5758.feature +++ /dev/null @@ -1 +0,0 @@ -Live Location Sharing - Update beacon info state event when sharing is ended \ No newline at end of file diff --git a/changelog.d/5762.sdk b/changelog.d/5762.sdk deleted file mode 100644 index 0bb89d4f2f..0000000000 --- a/changelog.d/5762.sdk +++ /dev/null @@ -1 +0,0 @@ -Some data classes are now immutable, using `val` instead of `var` \ No newline at end of file diff --git a/changelog.d/5765.bugfix b/changelog.d/5765.bugfix deleted file mode 100644 index ccf8c1b2c2..0000000000 --- a/changelog.d/5765.bugfix +++ /dev/null @@ -1 +0,0 @@ -Fixing setting transfer title in call transfer. diff --git a/changelog.d/5766.bugfix b/changelog.d/5766.bugfix deleted file mode 100644 index d3290decab..0000000000 --- a/changelog.d/5766.bugfix +++ /dev/null @@ -1 +0,0 @@ -Changes destination after joining a space to Explore Space Rooms screen diff --git a/changelog.d/5767.bugfix b/changelog.d/5767.bugfix deleted file mode 100644 index 2a34fbf4b3..0000000000 --- a/changelog.d/5767.bugfix +++ /dev/null @@ -1 +0,0 @@ -Unignoring a user will perform an initial sync \ No newline at end of file diff --git a/changelog.d/6025.misc b/changelog.d/6025.misc new file mode 100644 index 0000000000..2900796792 --- /dev/null +++ b/changelog.d/6025.misc @@ -0,0 +1 @@ +@Ignore a number of tests that are currently failing in CI. diff --git a/dependencies.gradle b/dependencies.gradle index ec27184d5c..36d7e425ac 100644 --- a/dependencies.gradle +++ b/dependencies.gradle @@ -7,26 +7,26 @@ ext.versions = [ 'targetCompat' : JavaVersion.VERSION_11, ] -def gradle = "7.0.4" +def gradle = "7.2.0" // Ref: https://kotlinlang.org/releases.html -def kotlin = "1.6.0" -def kotlinCoroutines = "1.6.0" -def dagger = "2.40.5" +def kotlin = "1.6.21" +def kotlinCoroutines = "1.6.1" +def dagger = "2.42" def retrofit = "2.9.0" def arrow = "0.8.2" def markwon = "4.6.2" def moshi = "1.13.0" -def lifecycle = "2.4.0" +def lifecycle = "2.4.1" def flowBinding = "1.2.0" def epoxy = "4.6.2" -def mavericks = "2.5.0" -def glide = "4.12.0" +def mavericks = "2.6.1" +def glide = "4.13.2" def bigImageViewer = "1.8.1" -def jjwt = "0.11.2" -def vanniktechEmoji = "0.8.0" +def jjwt = "0.11.5" +def vanniktechEmoji = "0.9.0" // Testing -def mockk = "1.12.1" +def mockk = "1.12.4" def espresso = "3.4.0" def androidxTest = "1.4.0" def androidxOrchestrator = "1.4.1" @@ -46,15 +46,15 @@ ext.libs = [ ], androidx : [ 'activity' : "androidx.activity:activity:1.2.4", - 'appCompat' : "androidx.appcompat:appcompat:1.4.0", + 'appCompat' : "androidx.appcompat:appcompat:1.4.1", 'core' : "androidx.core:core-ktx:1.7.0", 'recyclerview' : "androidx.recyclerview:recyclerview:1.2.1", 'exifinterface' : "androidx.exifinterface:exifinterface:1.3.3", - 'fragmentKtx' : "androidx.fragment:fragment-ktx:1.4.0", - 'constraintLayout' : "androidx.constraintlayout:constraintlayout:2.1.2", + 'fragmentKtx' : "androidx.fragment:fragment-ktx:1.4.1", + 'constraintLayout' : "androidx.constraintlayout:constraintlayout:2.1.3", 'work' : "androidx.work:work-runtime-ktx:2.7.1", 'autoFill' : "androidx.autofill:autofill:1.1.0", - 'preferenceKtx' : "androidx.preference:preference-ktx:1.1.1", + 'preferenceKtx' : "androidx.preference:preference-ktx:1.2.0", 'junit' : "androidx.test.ext:junit:1.1.3", 'lifecycleCommon' : "androidx.lifecycle:lifecycle-common:$lifecycle", 'lifecycleLivedata' : "androidx.lifecycle:lifecycle-livedata-ktx:$lifecycle", @@ -75,7 +75,7 @@ ext.libs = [ 'transition' : "androidx.transition:transition:1.2.0", ], google : [ - 'material' : "com.google.android.material:material:1.5.0" + 'material' : "com.google.android.material:material:1.6.0" ], dagger : [ 'dagger' : "com.google.dagger:dagger:$dagger", diff --git a/dependencies_groups.gradle b/dependencies_groups.gradle index 2c3f6a0219..76869fccf1 100644 --- a/dependencies_groups.gradle +++ b/dependencies_groups.gradle @@ -123,6 +123,7 @@ ext.groups = [ 'io.github.detekt.sarif4k', 'io.github.microutils', 'io.github.reactivecircus.flowbinding', + 'io.gitlab.arturbosch.detekt', 'io.grpc', 'io.jsonwebtoken', 'io.kindedj', @@ -164,6 +165,7 @@ ext.groups = [ 'org.codehaus.woodstox', 'org.eclipse.ee4j', 'org.ec4j.core', + 'org.freemarker', 'org.glassfish.jaxb', 'org.hamcrest', 'org.jacoco', @@ -194,6 +196,7 @@ ext.groups = [ 'org.testng', 'org.threeten', 'org.webjars', + 'org.yaml', 'ru.noties', 'xerces', 'xml-apis', diff --git a/docs/add_threePids.md b/docs/add_threePids.md index 89fc92f329..6fb0aff426 100644 --- a/docs/add_threePids.md +++ b/docs/add_threePids.md @@ -37,9 +37,9 @@ Wording: "We've sent you an email to verify your address. Please follow the inst } ``` -## User receive an e-mail +## User receives an e-mail -> [homeserver.org] Validate your email +> `homeserver.org` Validate your email > > A request to add an email address to your Matrix account has been received. If this was you, please click the link below to confirm adding this email: https://homeserver.org/_matrix/client/unstable/add_threepid/email/submit_token?token=WUnEhQAmJrXupdEbXgdWvnVIKaGYZFsU&client_secret=TixzvOnw7nLEUdiQEmkHzkXKrY4HhiGh&sid=bxyDHuJKsdkjMlTJ diff --git a/docs/integration_tests.md b/docs/integration_tests.md index 0fa1998499..e79f966d1f 100644 --- a/docs/integration_tests.md +++ b/docs/integration_tests.md @@ -43,14 +43,17 @@ virtualenv -p python3 env source env/bin/activate pip install -e . demo/start.sh --no-rate-limit + ``` -Alternatively, to install the latest Synapse release package (and not a cloned branch) you can run the following instead of `pip install -e .`: +Alternatively, to install the latest Synapse release package (and not a cloned branch) you can run the following instead of `git clone` and `pip install -e .`: ```bash pip install matrix-synapse ``` +On your first run, you will want to stop the demo and edit the config to correct the `public_baseurl` to http://10.0.2.2:8080 and restart the server. + You should now have 3 running federated Synapse instances 🎉, at http://127.0.0.1:8080/, http://127.0.0.1:8081/ and http://127.0.0.1:8082/, which should display a "It Works! Synapse is running" message. ## Run the test @@ -87,6 +90,18 @@ You'll need python3 to be able to run synapse Try on the Emulator browser to open "http://10.0.2.2:8080". You should see the "Synapse is running" message. +### Tests partially run but some fail with "Unable to contact localhost:8080" + +This is because the `public_baseurl` of synapse is not consistent with the endpoint that the tests are connecting to. + +Ensure you have the following configuration in `demo/etc/8080.config`. + +``` +public_baseurl: http://10.0.2.2:8080/ +``` + +After changing this you will need to restart synapse using `demo/stop.sh` and `demo/start.sh` to load the new configuration. + ### virtualenv command fails You can try using diff --git a/docs/pull_request.md b/docs/pull_request.md index b4dd0bd209..94f8f3b6d8 100644 --- a/docs/pull_request.md +++ b/docs/pull_request.md @@ -30,16 +30,30 @@ In any case, it is better to explicitly declare in the description why the PR is Also, draft PR should not stay indefinitely in this state. It may be removed if it is the case and the submitter does not update it after a few days. +##### Base branch + +The `develop` branch is generally the base branch for every PRs. + +Exceptions can occur: + +- if a feature implementation is split into multiple PRs. We can have a chain of PRs in this case. PR can be merged one by one on develop, and GitHub change the target branch to `develop` for the next PR automatically. +- we want to merge a PR from the community, but there is still work to do, and the PR is not updated by the submitter. First, we can kindly ask the submitter if they will update their PR, by commenting it. If there is no answer after a few days (including a week-end), we can create a new branch, push it, and change the target branch of the PR to this new branch. The PR can then be merged, and we can add more commits to fix the issues. After that a new PR can be created with `develop` as a target branch. + +**Important notice 1:** Releases are created from the `develop` branch. So `develop` branch should always contain a "releasable" source code. So when a feature is being implemented with several PRs, it has to be disabled by default (using a feature flag for instance), until the feature is fully implemented. A last PR to enable the feature can then be created. + +**Important notice 2:** Database migration: some developers and some people from the community are using the nightly build from `develop`. Multiple database migrations should be properly handled for them. This is OK to have multiple migrations between 2 releases, this is not OK to add steps to the pending database migration on `develop`. So for instance `develop` users will migrate from version 11 to version 12, then 13, then 14, and `main` users will do all those steps after they get the app upgrade. + ##### PR Review Assignment -We use automatic assignment for PR reviews. A PR is automatically routed by GitHub to a team member using the round robin algorithm. The process is the following: +We use automatic assignment for PR reviews. A PR is automatically routed by GitHub to 2 team members using the round robin algorithm. The process is the following: -- The PR creator assigns the [element-android](https://github.com/orgs/vector-im/teams/element-android) team as a reviewer. They can skip this process and assign directly a specific member if they think they should take a look at it. -- GitHub automatically assigns one reviewer. If the chosen reviewer is not available (holiday, etc.), remove them and set again the team, GitHub will select another reviewer. -- The reviewer gets a notification to make the review: they review the code following the good practice (see the rest of this document). +- The PR creator can assign specific people if they have another Android developer in their team or they think a specific reviewer should take a look at the PR. +- If there are missing reviewers, the PR creator assigns the [element-android-reviewers](https://github.com/orgs/vector-im/teams/element-android-reviewers) team as a reviewer. +- GitHub automatically assigns other reviewers. If one of the chosen reviewers is not available (holiday, etc.), remove them and set again the team, GitHub will select another reviewer. +- Reviewers get a notification to make the review: they review the code following the good practice (see the rest of this document). - After making their own review, if they feel not confident enough, they can ask another person for a full review, or they can tag someone within a PR comment to check specific lines. -For PRs coming from the community, the issue wrangler can assign either the team [element-android](https://github.com/orgs/vector-im/teams/element-android) or any member directly. +For PRs coming from the community, the issue wrangler can assign either the team [element-android-reviewers](https://github.com/orgs/vector-im/teams/element-android-reviewers) or any members directly. ##### PR review time @@ -102,7 +116,7 @@ Review such PR is the same recipe than for PR from Dependabot ##### Sync analytics plan This tools imports any update in the analytics plan. See instruction in the PR itself to handle it. -More info can be found in the file [analytics.md] +More info can be found in the file [analytics.md](./analytics.md) ## Reviewing PR @@ -233,4 +247,4 @@ Also "Resolve conversation" should probably be hit by the creator of the convers PR submitter is responsible of the incoming change. PR reviewers who approved the PR take a part of responsibility on the code which will land to develop, and then be used by our users, and the user of our forks. -That said, bug may still be merged on `develop`, this is still acceptable of course. In this case, please make sure an issue is created and correctly labelled. Ideally, such issues should be fixed before the next release candidate, i.e. with a higher priority. But as we release the application every 10 working days, it can be hard to fix every bugs. That's why PR should be fully tested and reviewed before being merge and we should never comment code review remark with "will be handled later", or similar comments. \ No newline at end of file +That said, bug may still be merged on `develop`, this is still acceptable of course. In this case, please make sure an issue is created and correctly labelled. Ideally, such issues should be fixed before the next release candidate, i.e. with a higher priority. But as we release the application every 10 working days, it can be hard to fix every bugs. That's why PR should be fully tested and reviewed before being merge and we should never comment code review remark with "will be handled later", or similar comments. diff --git a/docs/ui-tests.md b/docs/ui-tests.md index 05eb50f525..667a6ed7fb 100644 --- a/docs/ui-tests.md +++ b/docs/ui-tests.md @@ -176,4 +176,4 @@ class SettingsAdvancedRobot { clickOn(R.string.settings_developer_mode_summary) } } -``` \ No newline at end of file +``` diff --git a/fastlane/metadata/android/cs-CZ/changelogs/40104060.txt b/fastlane/metadata/android/cs-CZ/changelogs/40104060.txt new file mode 100644 index 0000000000..3eda022464 --- /dev/null +++ b/fastlane/metadata/android/cs-CZ/changelogs/40104060.txt @@ -0,0 +1,2 @@ +Hlavní změny v této verzi: Časová osa vláken je nyní živá a rychlejší. Opravy různých chyb a vylepšení stability. +Úplný seznam změn: https://github.com/vector-im/element-android/releases/tag/v1.4.6 diff --git a/fastlane/metadata/android/cs-CZ/changelogs/40104070.txt b/fastlane/metadata/android/cs-CZ/changelogs/40104070.txt new file mode 100644 index 0000000000..cba2012c1c --- /dev/null +++ b/fastlane/metadata/android/cs-CZ/changelogs/40104070.txt @@ -0,0 +1,2 @@ +Hlavní změny v této verzi: Opravy různých chyb a vylepšení stability. +Úplný seznam změn: https://github.com/vector-im/element-android/releases/tag/v1.4.7 diff --git a/fastlane/metadata/android/cs-CZ/changelogs/40104080.txt b/fastlane/metadata/android/cs-CZ/changelogs/40104080.txt new file mode 100644 index 0000000000..61e7fd7940 --- /dev/null +++ b/fastlane/metadata/android/cs-CZ/changelogs/40104080.txt @@ -0,0 +1,2 @@ +Hlavní změny v této verzi: Časová osa vlákna je nyní živá a rychlejší. Opravy různých chyb a vylepšení stability. +Úplný seznam změn: https://github.com/vector-im/element-android/releases diff --git a/fastlane/metadata/android/cs-CZ/changelogs/40104100.txt b/fastlane/metadata/android/cs-CZ/changelogs/40104100.txt new file mode 100644 index 0000000000..46a75b77a7 --- /dev/null +++ b/fastlane/metadata/android/cs-CZ/changelogs/40104100.txt @@ -0,0 +1,2 @@ +Hlavní změny v této verzi: Posun přehrávání v hlasových zprávách. Opravy různých chyb a vylepšení stability. +Úplný seznam změn: https://github.com/vector-im/element-android/releases diff --git a/fastlane/metadata/android/cs-CZ/changelogs/40104110.txt b/fastlane/metadata/android/cs-CZ/changelogs/40104110.txt new file mode 100644 index 0000000000..578549ce6c --- /dev/null +++ b/fastlane/metadata/android/cs-CZ/changelogs/40104110.txt @@ -0,0 +1,2 @@ +Hlavní změny v této verzi: Opravy různých chyb a vylepšení stability. +Úplný seznam změn: https://github.com/vector-im/element-android/releases diff --git a/fastlane/metadata/android/cs-CZ/changelogs/40104120.txt b/fastlane/metadata/android/cs-CZ/changelogs/40104120.txt new file mode 100644 index 0000000000..7867646fe5 --- /dev/null +++ b/fastlane/metadata/android/cs-CZ/changelogs/40104120.txt @@ -0,0 +1,2 @@ +Hlavní změny v této verzi: Umožňuje uživatelům zobrazovat se offline a přidává zvukový přehrávač pro zvukové přílohy +Úplný seznam změn: https://github.com/vector-im/element-android/releases diff --git a/fastlane/metadata/android/cs-CZ/changelogs/40104130.txt b/fastlane/metadata/android/cs-CZ/changelogs/40104130.txt new file mode 100644 index 0000000000..7867646fe5 --- /dev/null +++ b/fastlane/metadata/android/cs-CZ/changelogs/40104130.txt @@ -0,0 +1,2 @@ +Hlavní změny v této verzi: Umožňuje uživatelům zobrazovat se offline a přidává zvukový přehrávač pro zvukové přílohy +Úplný seznam změn: https://github.com/vector-im/element-android/releases diff --git a/fastlane/metadata/android/de-DE/changelogs/40104060.txt b/fastlane/metadata/android/de-DE/changelogs/40104060.txt new file mode 100644 index 0000000000..17cfdd26cc --- /dev/null +++ b/fastlane/metadata/android/de-DE/changelogs/40104060.txt @@ -0,0 +1,2 @@ +Hauptänderungen: Threads sind jetzt schneller, Fehlerbehebungen. +Alle Änderungen: https://github.com/vector-im/element-android/releases diff --git a/fastlane/metadata/android/de-DE/changelogs/40104070.txt b/fastlane/metadata/android/de-DE/changelogs/40104070.txt new file mode 100644 index 0000000000..30da225add --- /dev/null +++ b/fastlane/metadata/android/de-DE/changelogs/40104070.txt @@ -0,0 +1,2 @@ +Änderungen: Fehlerbehebungen +Alle Änderungen: https://github.com/vector-im/element-android/releases/tag/v1.4.7 diff --git a/fastlane/metadata/android/de-DE/changelogs/40104080.txt b/fastlane/metadata/android/de-DE/changelogs/40104080.txt new file mode 100644 index 0000000000..902e1d27f7 --- /dev/null +++ b/fastlane/metadata/android/de-DE/changelogs/40104080.txt @@ -0,0 +1,2 @@ +Hauptänderungen: Schnellere Threads, Fehlerbehebungen. +Alle Änderungen: https://github.com/vector-im/element-android/releases diff --git a/fastlane/metadata/android/de-DE/changelogs/40104100.txt b/fastlane/metadata/android/de-DE/changelogs/40104100.txt new file mode 100644 index 0000000000..2de5ec1d6a --- /dev/null +++ b/fastlane/metadata/android/de-DE/changelogs/40104100.txt @@ -0,0 +1,2 @@ +Hauptänderungen: Scrollen in Sprachnachrichten, Fehlerbehebungen. +Alle Änderungen: https://github.com/vector-im/element-android/releases diff --git a/fastlane/metadata/android/de-DE/changelogs/40104110.txt b/fastlane/metadata/android/de-DE/changelogs/40104110.txt new file mode 100644 index 0000000000..bde9f04e11 --- /dev/null +++ b/fastlane/metadata/android/de-DE/changelogs/40104110.txt @@ -0,0 +1,2 @@ +Änderungen: Fehlerbehebungen und Stabilitätsverbesserungen +Alle Änderungen: https://github.com/vector-im/element-android/releases diff --git a/fastlane/metadata/android/de-DE/changelogs/40104120.txt b/fastlane/metadata/android/de-DE/changelogs/40104120.txt new file mode 100644 index 0000000000..e0ce944874 --- /dev/null +++ b/fastlane/metadata/android/de-DE/changelogs/40104120.txt @@ -0,0 +1,2 @@ +Hauptänderungen: Nutzer können ihren Status auf „Offline“ setzen, Gesendete Audiodateien können nun in der App abgespielt werden +Alle Änderungen: https://github.com/vector-im/element-android/releases diff --git a/fastlane/metadata/android/de-DE/changelogs/40104130.txt b/fastlane/metadata/android/de-DE/changelogs/40104130.txt new file mode 100644 index 0000000000..e0ce944874 --- /dev/null +++ b/fastlane/metadata/android/de-DE/changelogs/40104130.txt @@ -0,0 +1,2 @@ +Hauptänderungen: Nutzer können ihren Status auf „Offline“ setzen, Gesendete Audiodateien können nun in der App abgespielt werden +Alle Änderungen: https://github.com/vector-im/element-android/releases diff --git a/fastlane/metadata/android/en-US/changelogs/40104120.txt b/fastlane/metadata/android/en-US/changelogs/40104120.txt new file mode 100644 index 0000000000..ea188c101c --- /dev/null +++ b/fastlane/metadata/android/en-US/changelogs/40104120.txt @@ -0,0 +1,2 @@ +Main changes in this version: Allows users to appear offline and adds an audio player for audio attachments +Full changelog: https://github.com/vector-im/element-android/releases diff --git a/fastlane/metadata/android/en-US/changelogs/40104130.txt b/fastlane/metadata/android/en-US/changelogs/40104130.txt new file mode 100644 index 0000000000..ea188c101c --- /dev/null +++ b/fastlane/metadata/android/en-US/changelogs/40104130.txt @@ -0,0 +1,2 @@ +Main changes in this version: Allows users to appear offline and adds an audio player for audio attachments +Full changelog: https://github.com/vector-im/element-android/releases diff --git a/fastlane/metadata/android/en-US/changelogs/40104140.txt b/fastlane/metadata/android/en-US/changelogs/40104140.txt new file mode 100644 index 0000000000..5cbd25f4d3 --- /dev/null +++ b/fastlane/metadata/android/en-US/changelogs/40104140.txt @@ -0,0 +1,2 @@ +Main changes in this version: Improve management of ignored users. Various bug fixes and stability improvements. +Full changelog: https://github.com/vector-im/element-android/releases diff --git a/fastlane/metadata/android/en-US/changelogs/40104160.txt b/fastlane/metadata/android/en-US/changelogs/40104160.txt new file mode 100644 index 0000000000..6e37a4ae7f --- /dev/null +++ b/fastlane/metadata/android/en-US/changelogs/40104160.txt @@ -0,0 +1,2 @@ +Main changes in this version: Better management of encrypted messages. Various bug fixes and stability improvements. +Full changelog: https://github.com/vector-im/element-android/releases diff --git a/fastlane/metadata/android/es-ES/changelogs/40103030.txt b/fastlane/metadata/android/es-ES/changelogs/40103030.txt new file mode 100644 index 0000000000..36f59a2308 --- /dev/null +++ b/fastlane/metadata/android/es-ES/changelogs/40103030.txt @@ -0,0 +1,2 @@ +Cambios principales en esta versión: Hacer la política de servidores de indentidad visible en los ajusted. Temporalmente quitar soporte para Android Auto. +Registro de cambios: https://github.com/vector-im/element-android/releases/tag/v1.3.3 diff --git a/fastlane/metadata/android/es-ES/changelogs/40104110.txt b/fastlane/metadata/android/es-ES/changelogs/40104110.txt new file mode 100644 index 0000000000..281df19388 --- /dev/null +++ b/fastlane/metadata/android/es-ES/changelogs/40104110.txt @@ -0,0 +1,2 @@ +Cambios principales en esta versión: Varias correciones de bugs y mejoras en la estabilidad +Registro de cambios: https://github.com/vector-im/element-android/releases diff --git a/fastlane/metadata/android/es-ES/full_description.txt b/fastlane/metadata/android/es-ES/full_description.txt index fdba15e90e..0e484158d9 100644 --- a/fastlane/metadata/android/es-ES/full_description.txt +++ b/fastlane/metadata/android/es-ES/full_description.txt @@ -37,3 +37,6 @@ Mensajería, llamadas de voz y video, uso compartido de archivos, uso compartido Continúa donde lo dejaste Manténgase en contacto donde quiera que esté con el historial de mensajes totalmente sincronizado en todos sus dispositivos y en la web en https://app.element.io + +Código abierto +Element Android es un proyecto de código abierto, alojado en GitHub. Porfavor, reporta bugs y problemas en esta dirección: https://github.com/vector-im/element-android diff --git a/fastlane/metadata/android/et/changelogs/40104060.txt b/fastlane/metadata/android/et/changelogs/40104060.txt new file mode 100644 index 0000000000..f506b617ed --- /dev/null +++ b/fastlane/metadata/android/et/changelogs/40104060.txt @@ -0,0 +1,2 @@ +Põhilised muutused selles versioonis: jutulõngad on nüüd kasutatavad ja toimivad kiiremini, lisaks pisiparandused ja stabiilsust parandavad kohendused. +Kogu ingliskeelne muudatuste logi: https://github.com/vector-im/element-android/releases/tag/v1.4.6 diff --git a/fastlane/metadata/android/et/changelogs/40104070.txt b/fastlane/metadata/android/et/changelogs/40104070.txt new file mode 100644 index 0000000000..ea3582678d --- /dev/null +++ b/fastlane/metadata/android/et/changelogs/40104070.txt @@ -0,0 +1,2 @@ +Põhilised muutused selles versioonis: pisiparandused ja stabiilsust parandavad kohendused. +Kogu ingliskeelne muudatuste logi: https://github.com/vector-im/element-android/releases/tag/v1.4.7 diff --git a/fastlane/metadata/android/et/changelogs/40104080.txt b/fastlane/metadata/android/et/changelogs/40104080.txt new file mode 100644 index 0000000000..a35fded9b6 --- /dev/null +++ b/fastlane/metadata/android/et/changelogs/40104080.txt @@ -0,0 +1,2 @@ +Põhilised muutused selles versioonis: jutulõngad on nüüd kasutatavad ja toimivad kiiremini, lisaks pisiparandused ja stabiilsust parandavad kohendused. +Kogu ingliskeelne muudatuste logi: https://github.com/vector-im/element-android/releases diff --git a/fastlane/metadata/android/et/changelogs/40104100.txt b/fastlane/metadata/android/et/changelogs/40104100.txt new file mode 100644 index 0000000000..b164935ba4 --- /dev/null +++ b/fastlane/metadata/android/et/changelogs/40104100.txt @@ -0,0 +1,2 @@ +Põhilised muutused selles versioonis: kerimine häälsõnumites ning erinevate vigade parandused ja stabiilsust edendavad kohendused. +Kogu ingliskeelne muudatuste logi: https://github.com/vector-im/element-android/releases diff --git a/fastlane/metadata/android/et/changelogs/40104110.txt b/fastlane/metadata/android/et/changelogs/40104110.txt new file mode 100644 index 0000000000..1df5ac4176 --- /dev/null +++ b/fastlane/metadata/android/et/changelogs/40104110.txt @@ -0,0 +1,2 @@ +Põhilised muutused selles versioonis: erinevate vigade parandused ja stabiilsust edendavad kohendused. +Kogu ingliskeelne muudatuste logi: https://github.com/vector-im/element-android/releases diff --git a/fastlane/metadata/android/et/changelogs/40104120.txt b/fastlane/metadata/android/et/changelogs/40104120.txt new file mode 100644 index 0000000000..1a7d3ae979 --- /dev/null +++ b/fastlane/metadata/android/et/changelogs/40104120.txt @@ -0,0 +1,2 @@ +Põhilised muutused selles versioonis: kasutajate võrguolekud ning helisõnumite esitaja. +Kogu ingliskeelne muudatuste logi: https://github.com/vector-im/element-android/releases diff --git a/fastlane/metadata/android/et/changelogs/40104130.txt b/fastlane/metadata/android/et/changelogs/40104130.txt new file mode 100644 index 0000000000..1a7d3ae979 --- /dev/null +++ b/fastlane/metadata/android/et/changelogs/40104130.txt @@ -0,0 +1,2 @@ +Põhilised muutused selles versioonis: kasutajate võrguolekud ning helisõnumite esitaja. +Kogu ingliskeelne muudatuste logi: https://github.com/vector-im/element-android/releases diff --git a/fastlane/metadata/android/fa/changelogs/40104060.txt b/fastlane/metadata/android/fa/changelogs/40104060.txt new file mode 100644 index 0000000000..5a1188b370 --- /dev/null +++ b/fastlane/metadata/android/fa/changelogs/40104060.txt @@ -0,0 +1,2 @@ +تغییرات عمده در این نگارش: خط زمانی رشته‌ها اکنون زنده و سریع‌تر است. چندین رفع اشکال و بهبود پایداری. +گزارش دگرگونی کامل: https://github.com/vector-im/element-android/releases/tag/v1.4.6 diff --git a/fastlane/metadata/android/fa/changelogs/40104070.txt b/fastlane/metadata/android/fa/changelogs/40104070.txt new file mode 100644 index 0000000000..d35ded337b --- /dev/null +++ b/fastlane/metadata/android/fa/changelogs/40104070.txt @@ -0,0 +1,2 @@ +تغییرات عمده در این نگارش: چندین رفع اشکال و بهبود پایداری. +گزارش دگرگونی کامل: https://github.com/vector-im/element-android/releases/tag/v1.4.7 diff --git a/fastlane/metadata/android/fa/changelogs/40104080.txt b/fastlane/metadata/android/fa/changelogs/40104080.txt new file mode 100644 index 0000000000..673a46a10d --- /dev/null +++ b/fastlane/metadata/android/fa/changelogs/40104080.txt @@ -0,0 +1,2 @@ +تغییرات عمده در این نگارش: خط زمانی رشته‌ها اکنون زنده و سریع‌تر است. چندین رفع اشکال و بهبود پایداری. +گزارش دگرگونی کامل: https://github.com/vector-im/element-android/releases diff --git a/fastlane/metadata/android/fa/changelogs/40104100.txt b/fastlane/metadata/android/fa/changelogs/40104100.txt new file mode 100644 index 0000000000..e7cdca6641 --- /dev/null +++ b/fastlane/metadata/android/fa/changelogs/40104100.txt @@ -0,0 +1,2 @@ +تغییرات عمده در این نگارش: لغزش در پیام‌های صوتی. رفع اشکال‌های مختلف و بهبودهای امنیتی. +گزارش دگرگونی کامل: https://github.com/vector-im/element-android/releases diff --git a/fastlane/metadata/android/fa/changelogs/40104110.txt b/fastlane/metadata/android/fa/changelogs/40104110.txt new file mode 100644 index 0000000000..29efb95925 --- /dev/null +++ b/fastlane/metadata/android/fa/changelogs/40104110.txt @@ -0,0 +1,2 @@ +تغییرات عمده در این نگارش: رفع اشکال‌های مختلف و بهبودهای پایداری. +گزارش دگرگونی کامل: https://github.com/vector-im/element-android/releases diff --git a/fastlane/metadata/android/fa/changelogs/40104120.txt b/fastlane/metadata/android/fa/changelogs/40104120.txt new file mode 100644 index 0000000000..4f730e52dc --- /dev/null +++ b/fastlane/metadata/android/fa/changelogs/40104120.txt @@ -0,0 +1,2 @@ +تغییرات عمده در این نگارش: اجازه به کاربران برای برون‌خط ظاهر شدن و افزودن یک پخش‌کنندهٔ صدا برای پیوست‌های صوتی +گزارش دگرگونی کامل: https://github.com/vector-im/element-android/releases diff --git a/fastlane/metadata/android/fa/changelogs/40104130.txt b/fastlane/metadata/android/fa/changelogs/40104130.txt new file mode 100644 index 0000000000..4f730e52dc --- /dev/null +++ b/fastlane/metadata/android/fa/changelogs/40104130.txt @@ -0,0 +1,2 @@ +تغییرات عمده در این نگارش: اجازه به کاربران برای برون‌خط ظاهر شدن و افزودن یک پخش‌کنندهٔ صدا برای پیوست‌های صوتی +گزارش دگرگونی کامل: https://github.com/vector-im/element-android/releases diff --git a/fastlane/metadata/android/fr-FR/changelogs/40104040.txt b/fastlane/metadata/android/fr-FR/changelogs/40104040.txt new file mode 100644 index 0000000000..af2d7bb086 --- /dev/null +++ b/fastlane/metadata/android/fr-FR/changelogs/40104040.txt @@ -0,0 +1,2 @@ +Principaux changements pour cette version : Mise-à-jour de l’interface de notification de rédaction en cours. Plusieurs corrections de bogues et d’améliorations de stabilité. +Intégralité des changements : https://github.com/vector-im/element-android/releases/tag/v1.4.4 diff --git a/fastlane/metadata/android/fr-FR/changelogs/40104060.txt b/fastlane/metadata/android/fr-FR/changelogs/40104060.txt new file mode 100644 index 0000000000..24e0fde3f3 --- /dev/null +++ b/fastlane/metadata/android/fr-FR/changelogs/40104060.txt @@ -0,0 +1,2 @@ +Principaux changements pour cette version : Les fils de discussion sont officiellement disponibles, et plus rapides. Plusieurs corrections de bogues et d’améliorations de stabilité. +Intégralité des changements : https://github.com/vector-im/element-android/releases/tag/v1.4.6 diff --git a/fastlane/metadata/android/fr-FR/changelogs/40104070.txt b/fastlane/metadata/android/fr-FR/changelogs/40104070.txt new file mode 100644 index 0000000000..3f9879d917 --- /dev/null +++ b/fastlane/metadata/android/fr-FR/changelogs/40104070.txt @@ -0,0 +1,2 @@ +Principaux changements pour cette version : Plusieurs corrections de bogues et d’améliorations de stabilité. +Intégralité des changements : https://github.com/vector-im/element-android/releases/tag/v1.4.7 diff --git a/fastlane/metadata/android/fr-FR/changelogs/40104080.txt b/fastlane/metadata/android/fr-FR/changelogs/40104080.txt new file mode 100644 index 0000000000..84f92c18e2 --- /dev/null +++ b/fastlane/metadata/android/fr-FR/changelogs/40104080.txt @@ -0,0 +1,2 @@ +Principaux changements pour cette version : Les fils de discussion sont officiellement disponibles, et plus rapides. Plusieurs corrections de bogues et d’améliorations de stabilité. +Intégralité des changements : https://github.com/vector-im/element-android/releases diff --git a/fastlane/metadata/android/hu-HU/changelogs/40104040.txt b/fastlane/metadata/android/hu-HU/changelogs/40104040.txt new file mode 100644 index 0000000000..60ec5256aa --- /dev/null +++ b/fastlane/metadata/android/hu-HU/changelogs/40104040.txt @@ -0,0 +1,2 @@ +Fő változás ebben a verzióban: Gépelés visszajelzési frissítések a felületen. További hibajavítások egy stabilitást növelő fejlesztések. +Teljes változásnapló: https://github.com/vector-im/element-android/releases/tag/v1.4.4 diff --git a/fastlane/metadata/android/hu-HU/changelogs/40104060.txt b/fastlane/metadata/android/hu-HU/changelogs/40104060.txt new file mode 100644 index 0000000000..a3f4b89d92 --- /dev/null +++ b/fastlane/metadata/android/hu-HU/changelogs/40104060.txt @@ -0,0 +1,2 @@ +Fő változás ebben a verzióban: Megjelentek az üzenetszálak az idővonalon és gyorsak. További hibajavítások és stabilitási fejlesztések. +Teljes változásnapló: https://github.com/vector-im/element-android/releases/tag/v1.4.6 diff --git a/fastlane/metadata/android/hu-HU/changelogs/40104070.txt b/fastlane/metadata/android/hu-HU/changelogs/40104070.txt new file mode 100644 index 0000000000..0f969fb577 --- /dev/null +++ b/fastlane/metadata/android/hu-HU/changelogs/40104070.txt @@ -0,0 +1,2 @@ +Fő változás ebben a verzióban: Hibajavítások és stabilizációs fejlesztések. +Teljes változásnapló: https://github.com/vector-im/element-android/releases/tag/v1.4.7 diff --git a/fastlane/metadata/android/hu-HU/changelogs/40104080.txt b/fastlane/metadata/android/hu-HU/changelogs/40104080.txt new file mode 100644 index 0000000000..c29b20c216 --- /dev/null +++ b/fastlane/metadata/android/hu-HU/changelogs/40104080.txt @@ -0,0 +1,2 @@ +Fő változás ebben a verzióban: Megjelentek az üzenetszálak az idővonalon és gyorsak. További hibajavítások és stabilitási fejlesztések. +Teljes változásnapló: https://github.com/vector-im/element-android/releases diff --git a/fastlane/metadata/android/id/changelogs/40104060.txt b/fastlane/metadata/android/id/changelogs/40104060.txt new file mode 100644 index 0000000000..3b4ce82497 --- /dev/null +++ b/fastlane/metadata/android/id/changelogs/40104060.txt @@ -0,0 +1,2 @@ +Perubahan utama dalam versi ini: Linimasa utasan sekarang langsung dan lebih cepat. Banyak perbaikan kutu dan perbaikan stabilitas. +Catatan perubahan lanjutan: https://github.com/vector-im/element-android/releases/tag/v1.4.6 diff --git a/fastlane/metadata/android/id/changelogs/40104070.txt b/fastlane/metadata/android/id/changelogs/40104070.txt new file mode 100644 index 0000000000..39daf31eb6 --- /dev/null +++ b/fastlane/metadata/android/id/changelogs/40104070.txt @@ -0,0 +1,2 @@ +Perubahan utama dalam versi ini: Banyak perbaikan kutu dan perbaikan stabilitas. +Catatan perubahan lanjutan: https://github.com/vector-im/element-android/releases/tag/v1.4.7 diff --git a/fastlane/metadata/android/id/changelogs/40104080.txt b/fastlane/metadata/android/id/changelogs/40104080.txt new file mode 100644 index 0000000000..0a5ae82096 --- /dev/null +++ b/fastlane/metadata/android/id/changelogs/40104080.txt @@ -0,0 +1,2 @@ +Perubahan utama dalam versi ini: Linimasa utasan sekarang langsung dan lebih cepat. Banyak perbaikan kutu dan perbaikan stabilitas. +Catatan perubahan lanjutan: https://github.com/vector-im/element-android/releases diff --git a/fastlane/metadata/android/id/changelogs/40104100.txt b/fastlane/metadata/android/id/changelogs/40104100.txt new file mode 100644 index 0000000000..3cda40aecc --- /dev/null +++ b/fastlane/metadata/android/id/changelogs/40104100.txt @@ -0,0 +1,2 @@ +Perubahan utama dalam versi ini: Gulir di pesan suara. Banyak perbaikan kutu dan perbaikan stabilitas. +Catatan perubahan lanjutan: https://github.com/vector-im/element-android/releases diff --git a/fastlane/metadata/android/id/changelogs/40104110.txt b/fastlane/metadata/android/id/changelogs/40104110.txt new file mode 100644 index 0000000000..1017951d47 --- /dev/null +++ b/fastlane/metadata/android/id/changelogs/40104110.txt @@ -0,0 +1,2 @@ +Perubahan utama dalam versi ini: Banyak perbaikan kutu dan perbaikan stabilitas. +Catatan perubahan lanjutan: https://github.com/vector-im/element-android/releases diff --git a/fastlane/metadata/android/id/changelogs/40104120.txt b/fastlane/metadata/android/id/changelogs/40104120.txt new file mode 100644 index 0000000000..ce1a4a4d84 --- /dev/null +++ b/fastlane/metadata/android/id/changelogs/40104120.txt @@ -0,0 +1,2 @@ +Perubahan utama dalam versi ini: Diperbolehkan pengguna untuk terlihat luring dan ditambahkan sebuah pemain audio untuk lampiran audio +Catatan perubahan lanjutan: https://github.com/vector-im/element-android/releases diff --git a/fastlane/metadata/android/id/changelogs/40104130.txt b/fastlane/metadata/android/id/changelogs/40104130.txt new file mode 100644 index 0000000000..ce1a4a4d84 --- /dev/null +++ b/fastlane/metadata/android/id/changelogs/40104130.txt @@ -0,0 +1,2 @@ +Perubahan utama dalam versi ini: Diperbolehkan pengguna untuk terlihat luring dan ditambahkan sebuah pemain audio untuk lampiran audio +Catatan perubahan lanjutan: https://github.com/vector-im/element-android/releases diff --git a/fastlane/metadata/android/it-IT/changelogs/40104060.txt b/fastlane/metadata/android/it-IT/changelogs/40104060.txt new file mode 100644 index 0000000000..c9ea8af2cc --- /dev/null +++ b/fastlane/metadata/android/it-IT/changelogs/40104060.txt @@ -0,0 +1,2 @@ +Modifiche principali in questa versione: la linea temporale per argomenti è ora attiva e più veloce. Varie correzioni e miglioramenti. +Cronologia completa: https://github.com/vector-im/element-android/releases/tag/v1.4.6 diff --git a/fastlane/metadata/android/it-IT/changelogs/40104070.txt b/fastlane/metadata/android/it-IT/changelogs/40104070.txt new file mode 100644 index 0000000000..2dfd415920 --- /dev/null +++ b/fastlane/metadata/android/it-IT/changelogs/40104070.txt @@ -0,0 +1,2 @@ +Modifiche principali in questa versione: varie correzioni di errori e miglioramenti della stabilità. +Cronologia completa: https://github.com/vector-im/element-android/releases/tag/v1.4.7 diff --git a/fastlane/metadata/android/it-IT/changelogs/40104080.txt b/fastlane/metadata/android/it-IT/changelogs/40104080.txt new file mode 100644 index 0000000000..8427e3d3fe --- /dev/null +++ b/fastlane/metadata/android/it-IT/changelogs/40104080.txt @@ -0,0 +1,2 @@ +Modifiche principali in questa versione: la linea temporale per argomenti è ora attiva e più veloce. Varie correzioni e miglioramenti. +Cronologia completa: https://github.com/vector-im/element-android/releases diff --git a/fastlane/metadata/android/it-IT/changelogs/40104100.txt b/fastlane/metadata/android/it-IT/changelogs/40104100.txt new file mode 100644 index 0000000000..6376e6c45a --- /dev/null +++ b/fastlane/metadata/android/it-IT/changelogs/40104100.txt @@ -0,0 +1,2 @@ +Modifiche principali in questa versione: scorrimento nei vocali. Varie correzioni e miglioramenti della stabilità. +Cronologia completa: https://github.com/vector-im/element-android/releases diff --git a/fastlane/metadata/android/it-IT/changelogs/40104110.txt b/fastlane/metadata/android/it-IT/changelogs/40104110.txt new file mode 100644 index 0000000000..556a6fc7ea --- /dev/null +++ b/fastlane/metadata/android/it-IT/changelogs/40104110.txt @@ -0,0 +1,2 @@ +Modifiche principali in questa versione: varie correzioni di errori e miglioramenti della stabilità. +Cronologia completa: https://github.com/vector-im/element-android/releases diff --git a/fastlane/metadata/android/it-IT/changelogs/40104120.txt b/fastlane/metadata/android/it-IT/changelogs/40104120.txt new file mode 100644 index 0000000000..fa015ae564 --- /dev/null +++ b/fastlane/metadata/android/it-IT/changelogs/40104120.txt @@ -0,0 +1,2 @@ +Modifiche principali in questa versione: consente agli utenti di apparire offline e aggiunge un player audio per gli allegati audio +Cronologia completa: https://github.com/vector-im/element-android/releases diff --git a/fastlane/metadata/android/it-IT/changelogs/40104130.txt b/fastlane/metadata/android/it-IT/changelogs/40104130.txt new file mode 100644 index 0000000000..fa015ae564 --- /dev/null +++ b/fastlane/metadata/android/it-IT/changelogs/40104130.txt @@ -0,0 +1,2 @@ +Modifiche principali in questa versione: consente agli utenti di apparire offline e aggiunge un player audio per gli allegati audio +Cronologia completa: https://github.com/vector-im/element-android/releases diff --git a/fastlane/metadata/android/lo-LA/changelogs/40100100.txt b/fastlane/metadata/android/lo-LA/changelogs/40100100.txt new file mode 100644 index 0000000000..fe0ee08327 --- /dev/null +++ b/fastlane/metadata/android/lo-LA/changelogs/40100100.txt @@ -0,0 +1,2 @@ +ສະບັບໃຫມ່ນີ້ສ່ວນໃຫຍ່ແມ່ນມີການແກ້ໄຂແລະການປັບປຸງ bug. ຕອນນີ້ການສົ່ງຂໍ້ຄວາມແມ່ນໄວຂຶ້ນຫຼາຍ. +ບັນທຶກການປ່ຽນແປງສະບັບເຕັມ: https://github.com/vector-im/element-android/releases/tag/v1.0.10 diff --git a/fastlane/metadata/android/lo-LA/changelogs/40100110.txt b/fastlane/metadata/android/lo-LA/changelogs/40100110.txt new file mode 100644 index 0000000000..69139783ad --- /dev/null +++ b/fastlane/metadata/android/lo-LA/changelogs/40100110.txt @@ -0,0 +1,2 @@ +ຮຸ່ນໃຫມ່ນີ້ສ່ວນໃຫຍ່ແມ່ນປະກອບດ້ວຍໜ້າຕາແອັບແລະການປັບປຸງປະສົບການຂອງຜູ້ໃຊ້. ດຽວນີ້ທ່ານສາມາດເຊີນໝູ່, ແລະສ້າງການສົນທະນາກົງໄດ້ໄວຂຶ້ນໂດຍການສະແກນລະຫັດ QR. +ບັນທຶກການປ່ຽນແປງສະບັບເຕັມ: https://github.com/vector-im/element-android/releases/tag/v1.0.11 diff --git a/fastlane/metadata/android/lo-LA/changelogs/40100120.txt b/fastlane/metadata/android/lo-LA/changelogs/40100120.txt new file mode 100644 index 0000000000..256cd1a437 --- /dev/null +++ b/fastlane/metadata/android/lo-LA/changelogs/40100120.txt @@ -0,0 +1,2 @@ +ການປ່ຽນແປງຫຼັກໃນສະບັບນີ້: ຕົວຢ່າງ URL, ແປ້ນພິມ Emoji ໃໝ່, ຄວາມສາມາດໃນການຕັ້ງຄ່າຫ້ອງໃໝ່, ແລະຫິມະສຳລັບວັນຄຣິດສະມາດ! +ບັນທຶກການປ່ຽນແປງສະບັບເຕັມ: https://github.com/vector-im/element-android/releases/tag/v1.0.12 diff --git a/fastlane/metadata/android/lo-LA/changelogs/40100130.txt b/fastlane/metadata/android/lo-LA/changelogs/40100130.txt new file mode 100644 index 0000000000..c4878ea5f6 --- /dev/null +++ b/fastlane/metadata/android/lo-LA/changelogs/40100130.txt @@ -0,0 +1,2 @@ +ການປ່ຽນແປງຫຼັກໃນສະບັບນີ້: ຕົວຢ່າງ URL, ແປ້ນພິມ Emoji ໃໝ່, ຄວາມສາມາດໃນການຕັ້ງຄ່າຫ້ອງໃໝ່, ແລະຫິມະສຳລັບວັນຄຣິດສະມາດ! +ບັນທຶກການປ່ຽນແປງສະບັບເຕັມ: https://github.com/vector-im/element-android/releases/tag/v1.0.13 diff --git a/fastlane/metadata/android/lo-LA/changelogs/40100140.txt b/fastlane/metadata/android/lo-LA/changelogs/40100140.txt new file mode 100644 index 0000000000..591b9b94b3 --- /dev/null +++ b/fastlane/metadata/android/lo-LA/changelogs/40100140.txt @@ -0,0 +1,2 @@ +ການປ່ຽນແປງຫຼັກໃນສະບັບນີ້: ແກ້ໄຂການອະນຸຍາດຫ້ອງ, ຮູບແບບ ແຈ້ງ / ມືດອັດຕະໂນມັດ, ແລະການແກ້ໄຂບັນຫາຈຳນວນໜຶ່ງ. +ບັນທຶກການປ່ຽນແປງສະບັບເຕັມ: https://github.com/vector-im/element-android/releases/tag/v1.0.14 diff --git a/fastlane/metadata/android/lo-LA/changelogs/40100150.txt b/fastlane/metadata/android/lo-LA/changelogs/40100150.txt new file mode 100644 index 0000000000..98577742c7 --- /dev/null +++ b/fastlane/metadata/android/lo-LA/changelogs/40100150.txt @@ -0,0 +1,2 @@ +ການປ່ຽນແປງຫຼັກໃນສະບັບນີ້: ຮັບຮອງການເຂົ້າລະບົບດ້ວຍສື່ສັງຄົມອອນລາຍ. +ບັນທຶກການປ່ຽນແປງສະບັບເຕັມ: https://github.com/vector-im/element-android/releases/tag/v1.0.15 diff --git a/fastlane/metadata/android/lo-LA/changelogs/40100160.txt b/fastlane/metadata/android/lo-LA/changelogs/40100160.txt new file mode 100644 index 0000000000..c369fc88d3 --- /dev/null +++ b/fastlane/metadata/android/lo-LA/changelogs/40100160.txt @@ -0,0 +1,2 @@ +ການປ່ຽນແປງຫຼັກໃນສະບັບນີ້: ຮັບຮອງການເຂົ້າລະບົບດ້ວຍສື່ສັງຄົມອອນລາຍ. +ບັນທຶກການປ່ຽນແປງສະບັບເຕັມ: https://github.com/vector-im/element-android/releases/tag/v1.0.15 and https://github.com/vector-im/element-android/releases/tag/v1.0.16 diff --git a/fastlane/metadata/android/lo-LA/changelogs/40100170.txt b/fastlane/metadata/android/lo-LA/changelogs/40100170.txt new file mode 100644 index 0000000000..776e1b6082 --- /dev/null +++ b/fastlane/metadata/android/lo-LA/changelogs/40100170.txt @@ -0,0 +1,2 @@ +ການປ່ຽນແປງຫຼັກໃນສະບັບນີ້: ແກ້ໄຂຂໍ້ຜິດພາດ! +ບັນທຶກການປ່ຽນແປງສະບັບເຕັມ: https://github.com/vector-im/element-android/releases/tag/v1.0.17 diff --git a/fastlane/metadata/android/lo-LA/changelogs/40101000.txt b/fastlane/metadata/android/lo-LA/changelogs/40101000.txt new file mode 100644 index 0000000000..f7d621404a --- /dev/null +++ b/fastlane/metadata/android/lo-LA/changelogs/40101000.txt @@ -0,0 +1,2 @@ +ການປ່ຽນແປງຫຼັກໃນສະບັບນີ້: ປັບປຸງປະສິດທິພາບຂອງ VoIP (ໂທສຽງ ແລະໂທວີດີໂອໃນການສົນທະນາກົງ) ແລະແກ້ໄຂຂໍ້ຜິດພາດ! +ບັນທຶກການປ່ຽນແປງສະບັບເຕັມ: https://github.com/vector-im/element-android/releases/tag/v1.1.0 diff --git a/fastlane/metadata/android/lo-LA/changelogs/40101010.txt b/fastlane/metadata/android/lo-LA/changelogs/40101010.txt new file mode 100644 index 0000000000..e2e1778b7a --- /dev/null +++ b/fastlane/metadata/android/lo-LA/changelogs/40101010.txt @@ -0,0 +1,2 @@ +ການປ່ຽນແປງຫຼັກໃນສະບັບນີ້: ປັບປຸງປະສິດທິພາບ ແລະແກ້ໄຂຂໍ້ຜິດພາດ! +ບັນທຶກການປ່ຽນແປງສະບັບເຕັມ: https://github.com/vector-im/element-android/releases/tag/v1.1.1 diff --git a/fastlane/metadata/android/lo-LA/changelogs/40101020.txt b/fastlane/metadata/android/lo-LA/changelogs/40101020.txt new file mode 100644 index 0000000000..581db95e90 --- /dev/null +++ b/fastlane/metadata/android/lo-LA/changelogs/40101020.txt @@ -0,0 +1,2 @@ +ການປ່ຽນແປງຫຼັກໃນສະບັບນີ້: ປັບປຸງປະສິດທິພາບ ແລະແກ້ໄຂຂໍ້ຜິດພາດ! +ບັນທຶກການປ່ຽນແປງສະບັບເຕັມ: https://github.com/vector-im/element-android/releases/tag/v1.1.2 diff --git a/fastlane/metadata/android/lo-LA/changelogs/40101030.txt b/fastlane/metadata/android/lo-LA/changelogs/40101030.txt new file mode 100644 index 0000000000..46d861995f --- /dev/null +++ b/fastlane/metadata/android/lo-LA/changelogs/40101030.txt @@ -0,0 +1,2 @@ +ການປ່ຽນແປງຫຼັກໃນສະບັບນີ້: ປັບປຸງປະສິດທິພາບ ແລະແກ້ໄຂຂໍ້ຜິດພາດ! +ບັນທຶກການປ່ຽນແປງສະບັບເຕັມ: https://github.com/vector-im/element-android/releases/tag/v1.1.3 diff --git a/fastlane/metadata/android/lo-LA/changelogs/40101040.txt b/fastlane/metadata/android/lo-LA/changelogs/40101040.txt new file mode 100644 index 0000000000..8ea7cac4f4 --- /dev/null +++ b/fastlane/metadata/android/lo-LA/changelogs/40101040.txt @@ -0,0 +1,2 @@ +ການປ່ຽນແປງຫຼັກໃນສະບັບນີ້: ປັບປຸງປະສິດທິພາບ ແລະແກ້ໄຂຂໍ້ຜິດພາດ! +ບັນທຶກການປ່ຽນແປງສະບັບເຕັມ: https://github.com/vector-im/element-android/releases/tag/v1.1.4 diff --git a/fastlane/metadata/android/lo-LA/changelogs/40101050.txt b/fastlane/metadata/android/lo-LA/changelogs/40101050.txt new file mode 100644 index 0000000000..2823c04b06 --- /dev/null +++ b/fastlane/metadata/android/lo-LA/changelogs/40101050.txt @@ -0,0 +1,2 @@ +ການປ່ຽນແປງຫຼັກໃນສະບັບນີ້: ການແກ້ບັນຫາດ່ວນສໍາລັບ 1.1.4 +ບັນທຶກການປ່ຽນແປງສະບັບເຕັມ: https://github.com/vector-im/element-android/releases/tag/v1.1.5 diff --git a/fastlane/metadata/android/lo-LA/changelogs/40101060.txt b/fastlane/metadata/android/lo-LA/changelogs/40101060.txt new file mode 100644 index 0000000000..0cdea9e2c7 --- /dev/null +++ b/fastlane/metadata/android/lo-LA/changelogs/40101060.txt @@ -0,0 +1,2 @@ +ການປ່ຽນແປງຫຼັກໃນສະບັບນີ້: ການແກ້ບັນຫາດ່ວນສໍາລັບ 1.1.5 +ບັນທຶກການປ່ຽນແປງສະບັບເຕັມ: https://github.com/vector-im/element-android/releases/tag/v1.1.6 diff --git a/fastlane/metadata/android/lo-LA/changelogs/40101070.txt b/fastlane/metadata/android/lo-LA/changelogs/40101070.txt new file mode 100644 index 0000000000..08926229f9 --- /dev/null +++ b/fastlane/metadata/android/lo-LA/changelogs/40101070.txt @@ -0,0 +1,2 @@ +ການປ່ຽນແປງຫຼັກໃນສະບັບນີ້: ຮອງຮັບເບຕ້າສຳລັບ Spaces. ບີບອັດວິດີໂອກ່ອນທີ່ຈະສົ່ງ. +ບັນທຶກການປ່ຽນແປງສະບັບເຕັມ: https://github.com/vector-im/element-android/releases/tag/v1.1.7 diff --git a/fastlane/metadata/android/lo-LA/changelogs/40101080.txt b/fastlane/metadata/android/lo-LA/changelogs/40101080.txt new file mode 100644 index 0000000000..f2947aae63 --- /dev/null +++ b/fastlane/metadata/android/lo-LA/changelogs/40101080.txt @@ -0,0 +1,2 @@ +ການປ່ຽນແປງຫຼັກໃນສະບັບນີ້: ການປັບປຸງສໍາລັບ Spaces. +ບັນທຶກການປ່ຽນແປງສະບັບເຕັມ: https://github.com/vector-im/element-android/releases/tag/v1.1.8 diff --git a/fastlane/metadata/android/lo-LA/changelogs/40101090.txt b/fastlane/metadata/android/lo-LA/changelogs/40101090.txt new file mode 100644 index 0000000000..129f86c562 --- /dev/null +++ b/fastlane/metadata/android/lo-LA/changelogs/40101090.txt @@ -0,0 +1,2 @@ +ການປ່ຽນແປງຫຼັກໃນສະບັບນີ້: ເພີ່ມການຮັບຮອງສໍາລັບເຄືອຂ່າຍ gitter.im. +ບັນທຶກການປ່ຽນແປງສະບັບເຕັມ: https://github.com/vector-im/element-android/releases/tag/v1.1.9 diff --git a/fastlane/metadata/android/lo-LA/changelogs/40101100.txt b/fastlane/metadata/android/lo-LA/changelogs/40101100.txt new file mode 100644 index 0000000000..337137d3bc --- /dev/null +++ b/fastlane/metadata/android/lo-LA/changelogs/40101100.txt @@ -0,0 +1,2 @@ +ການປ່ຽນແປງຫຼັກໃນສະບັບນີ້: ອັບເດດຮູບແບບສີສັນ ແລະຮູບແບບສີສັນ ແລະຄຸນສົມບັດໃໝ່ສຳລັບຊ່ອງຫວ່າງ. +ບັນທຶກການປ່ຽນແປງສະບັບເຕັມ: https://github.com/vector-im/element-android/releases/tag/v1.1.10 diff --git a/fastlane/metadata/android/lo-LA/changelogs/40101110.txt b/fastlane/metadata/android/lo-LA/changelogs/40101110.txt new file mode 100644 index 0000000000..f95972eaa9 --- /dev/null +++ b/fastlane/metadata/android/lo-LA/changelogs/40101110.txt @@ -0,0 +1,2 @@ +ການປ່ຽນແປງຫຼັກໃນສະບັບນີ້: ອັບເດດຮູບແບບສີສັນ ແລະຮູບແບບສີສັນ ແລະຄຸນສົມບັດໃໝ່ສຳລັບຊ່ອງຫວ່າງ (bugfix ສຳລັບ 1.1.10) +ບັນທຶກການປ່ຽນແປງສະບັບເຕັມ: https://github.com/vector-im/element-android/releases/tag/v1.1.11 diff --git a/fastlane/metadata/android/lo-LA/changelogs/40101120.txt b/fastlane/metadata/android/lo-LA/changelogs/40101120.txt new file mode 100644 index 0000000000..a613e176aa --- /dev/null +++ b/fastlane/metadata/android/lo-LA/changelogs/40101120.txt @@ -0,0 +1,2 @@ +ການປ່ຽນແປງຫຼັກໃນສະບັບນີ້: ອັບເດດຮູບແບບສີສັນ ແລະຮູບແບບ ແລະແກ້ໄຂການຂັດຂ້ອງຫຼັງຈາກການໂທວິດີໂອ +ບັນທຶກການປ່ຽນແປງສະບັບເຕັມ: https://github.com/vector-im/element-android/releases/tag/v1.1.12 diff --git a/fastlane/metadata/android/lo-LA/changelogs/40101130.txt b/fastlane/metadata/android/lo-LA/changelogs/40101130.txt new file mode 100644 index 0000000000..3cc85bc6f8 --- /dev/null +++ b/fastlane/metadata/android/lo-LA/changelogs/40101130.txt @@ -0,0 +1,2 @@ +ການປ່ຽນແປງຫຼັກໃນສະບັບນີ້: ແກ້ໄຂບັນຫາກ່ຽວຄວາມສະຖຽນ. +ບັນທຶກການປ່ຽນແປງສະບັບເຕັມ: https://github.com/vector-im/element-android/releases/tag/v1.1.13 diff --git a/fastlane/metadata/android/lo-LA/changelogs/40101140.txt b/fastlane/metadata/android/lo-LA/changelogs/40101140.txt new file mode 100644 index 0000000000..11a5df1059 --- /dev/null +++ b/fastlane/metadata/android/lo-LA/changelogs/40101140.txt @@ -0,0 +1,2 @@ +ການປ່ຽນແປງຫຼັກໃນສະບັບນີ້: ແກ້ໄຂບັນຫາກ່ຽວກັບຂໍ້ຄວາມທີ່ຖືກເຂົ້າລະຫັດ. +ບັນທຶກການປ່ຽນແປງສະບັບເຕັມ: https://github.com/vector-im/element-android/releases/tag/v1.1.14 diff --git a/fastlane/metadata/android/lo-LA/changelogs/40101150.txt b/fastlane/metadata/android/lo-LA/changelogs/40101150.txt new file mode 100644 index 0000000000..8751496a49 --- /dev/null +++ b/fastlane/metadata/android/lo-LA/changelogs/40101150.txt @@ -0,0 +1,2 @@ +ການປ່ຽນແປງຫຼັກໃນສະບັບນີ້: ການປະຕິບັດຂໍ້ຄວາມສຽງພາຍໃຕ້ການຕັ້ງຄ່າຫ້ອງທົດລອງ. +ບັນທຶກການປ່ຽນແປງສະບັບເຕັມ: https://github.com/vector-im/element-android/releases/tag/v1.1.15 diff --git a/fastlane/metadata/android/lo-LA/changelogs/40101160.txt b/fastlane/metadata/android/lo-LA/changelogs/40101160.txt new file mode 100644 index 0000000000..c102140102 --- /dev/null +++ b/fastlane/metadata/android/lo-LA/changelogs/40101160.txt @@ -0,0 +1,2 @@ +ການປ່ຽນແປງຫຼັກໃນສະບັບນີ້: ແກ້ໄຂຂໍ້ຜິດພາດໃນເວລາສົ່ງຂໍ້ຄວາມເຂົ້າລະຫັດຖ້າມີຄົນຢູ່ໃນຫ້ອງອອກຈາກລະບົບ. +ບັນທຶກການປ່ຽນແປງສະບັບເຕັມ: https://github.com/vector-im/element-android/releases/tag/v1.1.16 diff --git a/fastlane/metadata/android/lo-LA/changelogs/40102000.txt b/fastlane/metadata/android/lo-LA/changelogs/40102000.txt new file mode 100644 index 0000000000..69f1667b93 --- /dev/null +++ b/fastlane/metadata/android/lo-LA/changelogs/40102000.txt @@ -0,0 +1,2 @@ +ການປ່ຽນແປງຫຼັກໃນສະບັບນີ້: ການສົນທະນາດ້ວຍສຽງເປີດໄວ້ໂດຍມາດຕະຖານ. +ບັນທຶກການປ່ຽນແປງສະບັບເຕັມ: https://github.com/vector-im/element-android/releases/tag/v1.2.0 diff --git a/fastlane/metadata/android/lo-LA/changelogs/40102010.txt b/fastlane/metadata/android/lo-LA/changelogs/40102010.txt new file mode 100644 index 0000000000..237b48ba3b --- /dev/null +++ b/fastlane/metadata/android/lo-LA/changelogs/40102010.txt @@ -0,0 +1,2 @@ +ການປ່ຽນແປງຫຼັກໃນສະບັບນີ້: ການປັບປຸງຫຼາຍຢ່າງກ່ຽວກັບ VoIP ແລະ Spaces (ຍັງຢູ່ໃນເບຕ້າ). +ບັນທຶກການປ່ຽນແປງສະບັບເຕັມ: https://github.com/vector-im/element-android/releases/tag/v1.2.1 diff --git a/fastlane/metadata/android/lo-LA/changelogs/40103000.txt b/fastlane/metadata/android/lo-LA/changelogs/40103000.txt new file mode 100644 index 0000000000..8e386df4a7 --- /dev/null +++ b/fastlane/metadata/android/lo-LA/changelogs/40103000.txt @@ -0,0 +1,2 @@ +ການປ່ຽນແປງຫຼັກໃນສະບັບນີ້: ຈັດລະບຽບຫ້ອງຂອງທ່ານໂດຍໃຊ້ Spaces! +ບັນທຶກການປ່ຽນແປງສະບັບເຕັມ: https://github.com/vector-im/element-android/releases/tag/v1.3.0 diff --git a/fastlane/metadata/android/lo-LA/changelogs/40103010.txt b/fastlane/metadata/android/lo-LA/changelogs/40103010.txt new file mode 100644 index 0000000000..0a6a6461fd --- /dev/null +++ b/fastlane/metadata/android/lo-LA/changelogs/40103010.txt @@ -0,0 +1,2 @@ +ການປ່ຽນແປງຫຼັກໃນສະບັບນີ້: ຈັດລະບຽບຫ້ອງຂອງທ່ານໂດຍໃຊ້ Spaces! v1.3.1 ກຳລັງແກ້ໄຂບັນແອັບໃຊ້ບໍ່ໄດ້ທີ່ສາມາດເກີດຂຶ້ນໃນ v1.3.0. +ບັນທຶກການປ່ຽນແປງສະບັບເຕັມ: https://github.com/vector-im/element-android/releases/tag/v1.3.1 diff --git a/fastlane/metadata/android/lo-LA/changelogs/40103020.txt b/fastlane/metadata/android/lo-LA/changelogs/40103020.txt new file mode 100644 index 0000000000..8b9ed12a3a --- /dev/null +++ b/fastlane/metadata/android/lo-LA/changelogs/40103020.txt @@ -0,0 +1,2 @@ +ການປ່ຽນແປງຫຼັກໃນສະບັບນີ້: ເພີ່ມການຮອງຮັບ Android Auto. ການແກ້ໄຂຍັກຫຼາຍອັນ! +ບັນທຶກການປ່ຽນແປງສະບັບເຕັມ: https://github.com/vector-im/element-android/releases/tag/v1.3.2 diff --git a/fastlane/metadata/android/lo-LA/changelogs/40103030.txt b/fastlane/metadata/android/lo-LA/changelogs/40103030.txt new file mode 100644 index 0000000000..6374f4fdf5 --- /dev/null +++ b/fastlane/metadata/android/lo-LA/changelogs/40103030.txt @@ -0,0 +1,2 @@ +ການປ່ຽນແປງຫຼັກໃນສະບັບນີ້: ເຮັດໃຫ້ນະໂຍບາຍຂອງເຄື່ອງແມ່ຂ່າຍຢັ້ງຢືນຕົວຕົນ (ຫຼາຍໜ່ວຍ) ໃຫ້ເຫັນຢູ່ໃນການຕັ້ງຄ່າ. ລຶບການຮອງຮັບ Android Auto ອອກຊົ່ວຄາວ. +ບັນທຶກການປ່ຽນແປງສະບັບເຕັມ: https://github.com/vector-im/element-android/releases/tag/v1.3.3 diff --git a/fastlane/metadata/android/lo-LA/changelogs/40103040.txt b/fastlane/metadata/android/lo-LA/changelogs/40103040.txt new file mode 100644 index 0000000000..896d9190ce --- /dev/null +++ b/fastlane/metadata/android/lo-LA/changelogs/40103040.txt @@ -0,0 +1,2 @@ +ການປ່ຽນແປງຫຼັກໃນສະບັບນີ້: ເພີ່ມການຮອງຮັບການມີຢູ່, ສໍາລັບຫ້ອງຂໍ້ຄວາມໂດຍກົງ (ໝາຍເຫດ: ການມີຢູ່ຖືກປິດການໃຊ້ງານຢູ່ໃນ matrix.org). ເພີ່ມການຮອງຮັບ Android Auto ອີກຄັ້ງ. +ບັນທຶກການປ່ຽນແປງສະບັບເຕັມ: https://github.com/vector-im/element-android/releases/tag/v1.3.4 diff --git a/fastlane/metadata/android/lo-LA/changelogs/40103050.txt b/fastlane/metadata/android/lo-LA/changelogs/40103050.txt new file mode 100644 index 0000000000..b76db1a80c --- /dev/null +++ b/fastlane/metadata/android/lo-LA/changelogs/40103050.txt @@ -0,0 +1,2 @@ +ການປ່ຽນແປງຫຼັກໃນສະບັບນີ້: ເພີ່ມການຮອງຮັບການມີຢູ່, ສໍາລັບຫ້ອງຂໍ້ຄວາມໂດຍກົງ (ໝາຍເຫດ: ການມີຢູ່ຖືກປິດການໃຊ້ງານຢູ່ໃນ matrix.org). ເພີ່ມການຮອງຮັບ Android Auto ອີກຄັ້ງ. +ບັນທຶກການປ່ຽນແປງສະບັບເຕັມ: https://github.com/vector-im/element-android/releases/tag/v1.3.5 diff --git a/fastlane/metadata/android/lo-LA/changelogs/40103060.txt b/fastlane/metadata/android/lo-LA/changelogs/40103060.txt new file mode 100644 index 0000000000..2e97183a56 --- /dev/null +++ b/fastlane/metadata/android/lo-LA/changelogs/40103060.txt @@ -0,0 +1,2 @@ +ການປ່ຽນແປງຫຼັກໃນສະບັບນີ້: ເພີ່ມການຮອງຮັບການມີຢູ່, ສໍາລັບຫ້ອງຂໍ້ຄວາມໂດຍກົງ (ໝາຍເຫດ: ການມີຢູ່ຖືກປິດການໃຊ້ງານຢູ່ໃນ matrix.org). ເພີ່ມການຮອງຮັບ Android Auto ອີກຄັ້ງ. +ບັນທຶກການປ່ຽນແປງສະບັບເຕັມ: https://github.com/vector-im/element-android/releases/tag/v1.3.6 diff --git a/fastlane/metadata/android/lo-LA/changelogs/40103070.txt b/fastlane/metadata/android/lo-LA/changelogs/40103070.txt new file mode 100644 index 0000000000..47eb6c7b4d --- /dev/null +++ b/fastlane/metadata/android/lo-LA/changelogs/40103070.txt @@ -0,0 +1,2 @@ +ການປ່ຽນແປງຫຼັກໃນສະບັບນີ້: ແກ້ໄຂບັກສ່ວນໃຫຍ່ກ່ຽວກັບການແຈ້ງເຕືອນ. +ບັນທຶກການປ່ຽນແປງສະບັບເຕັມ: https://github.com/vector-im/element-android/releases/tag/v1.3.7-RC2 diff --git a/fastlane/metadata/android/lo-LA/changelogs/40103080.txt b/fastlane/metadata/android/lo-LA/changelogs/40103080.txt new file mode 100644 index 0000000000..a12d374d20 --- /dev/null +++ b/fastlane/metadata/android/lo-LA/changelogs/40103080.txt @@ -0,0 +1,2 @@ +ການປ່ຽນແປງຫຼັກໃນສະບັບນີ້: ແກ້ໄຂບັກ! +ບັນທຶກການປ່ຽນແປງສະບັບເຕັມ: https://github.com/vector-im/element-android/releases/tag/v1.3.8 diff --git a/fastlane/metadata/android/lo-LA/changelogs/40103090.txt b/fastlane/metadata/android/lo-LA/changelogs/40103090.txt new file mode 100644 index 0000000000..ee93ca0a96 --- /dev/null +++ b/fastlane/metadata/android/lo-LA/changelogs/40103090.txt @@ -0,0 +1,2 @@ +ການປ່ຽນແປງຫຼັກໃນສະບັບນີ້: ເພີ່ມການຮອງຮັບສະບັບຮ່າງການສົນທະນາດ້ວຍສຽງ. ແກ້ໄຂບັກຫຼາຍໆອັນ! +ບັນທຶກການປ່ຽນແປງສະບັບເຕັມ: https://github.com/vector-im/element-android/releases/tag/v1.3.09 diff --git a/fastlane/metadata/android/lo-LA/changelogs/40103100.txt b/fastlane/metadata/android/lo-LA/changelogs/40103100.txt new file mode 100644 index 0000000000..b8b65387ef --- /dev/null +++ b/fastlane/metadata/android/lo-LA/changelogs/40103100.txt @@ -0,0 +1,2 @@ +ການປ່ຽນແປງຫຼັກໃນສະບັບນີ້: ເພີ່ມການຮອງຮັບແບບສໍາຫຼວດ (ໃນຫ້ອງທົດລອງ). ການອອກແບບຕົວຢ່າງ URL ໃໝ່. +ບັນທຶກການປ່ຽນແປງສະບັບເຕັມ: https://github.com/vector-im/element-android/releases/tag/v1.3.10 diff --git a/fastlane/metadata/android/lo-LA/changelogs/40103110.txt b/fastlane/metadata/android/lo-LA/changelogs/40103110.txt new file mode 100644 index 0000000000..f64ee3b469 --- /dev/null +++ b/fastlane/metadata/android/lo-LA/changelogs/40103110.txt @@ -0,0 +1,2 @@ +ການປ່ຽນແປງຫຼັກໃນສະບັບນີ້: ແກ້ໄຂບັກ! +ບັນທຶກການປ່ຽນແປງສະບັບເຕັມ: https://github.com/vector-im/element-android/releases/tag/v1.3.11 diff --git a/fastlane/metadata/android/lo-LA/changelogs/40103120.txt b/fastlane/metadata/android/lo-LA/changelogs/40103120.txt new file mode 100644 index 0000000000..a7b8412cfb --- /dev/null +++ b/fastlane/metadata/android/lo-LA/changelogs/40103120.txt @@ -0,0 +1,2 @@ +ການປ່ຽນແປງຫຼັກໃນສະບັບນີ້: ແກ້ໄຂບັກ!. +ບັນທຶກການປ່ຽນແປງສະບັບເຕັມ: https://github.com/vector-im/element-android/releases/tag/v1.3.12 diff --git a/fastlane/metadata/android/lo-LA/changelogs/40103130.txt b/fastlane/metadata/android/lo-LA/changelogs/40103130.txt new file mode 100644 index 0000000000..cb725ee68c --- /dev/null +++ b/fastlane/metadata/android/lo-LA/changelogs/40103130.txt @@ -0,0 +1,2 @@ +ການປ່ຽນແປງຫຼັກໃນສະບັບນີ້: ການປ່ຽນແປງຄັ້ງທໍາອິດໃນຫນ້າຈໍເລີ່ມຕົ້ນ, ລວມທັງການວິເຄາະເລືອກເຂົ້າຮ່ວມ. ຮອງຮັບເຫດການທີ່ມີຄະນິດສາດເພີ່ມຢູ່ໃນຫ້ອງທົດລອງ. +ບັນທຶກການປ່ຽນແປງສະບັບເຕັມ: https://github.com/vector-im/element-android/releases/tag/v1.3.13 diff --git a/fastlane/metadata/android/lo-LA/changelogs/40103140.txt b/fastlane/metadata/android/lo-LA/changelogs/40103140.txt new file mode 100644 index 0000000000..1f72757cf1 --- /dev/null +++ b/fastlane/metadata/android/lo-LA/changelogs/40103140.txt @@ -0,0 +1,2 @@ +ການປ່ຽນແປງຫຼັກໃນສະບັບນີ້: ການປ່ຽນແປງຄັ້ງທໍາອິດໃນຫນ້າຈໍເລີ່ມຕົ້ນ, ລວມທັງການວິເຄາະເລືອກເຂົ້າຮ່ວມ. ຮອງຮັບເຫດການທີ່ມີຄະນິດສາດເພີ່ມຢູ່ໃນຫ້ອງທົດລອງ. +ບັນທຶກການປ່ຽນແປງສະບັບເຕັມ: https://github.com/vector-im/element-android/releases/tag/v1.3.14 diff --git a/fastlane/metadata/android/lo-LA/changelogs/40103150.txt b/fastlane/metadata/android/lo-LA/changelogs/40103150.txt new file mode 100644 index 0000000000..c11acd15d6 --- /dev/null +++ b/fastlane/metadata/android/lo-LA/changelogs/40103150.txt @@ -0,0 +1,2 @@ +ການປ່ຽນແປງຫຼັກໃນສະບັບນີ້: ການປ່ຽນແປງຄັ້ງທໍາອິດໃນຫນ້າຈໍເລີ່ມຕົ້ນ, ລວມທັງການວິເຄາະເລືອກເຂົ້າຮ່ວມ. ຮອງຮັບເຫດການທີ່ມີຄະນິດສາດເພີ່ມຢູ່ໃນຫ້ອງທົດລອງ. +ບັນທຶກການປ່ຽນແປງສະບັບເຕັມ: https://github.com/vector-im/element-android/releases/tag/v1.3.15 diff --git a/fastlane/metadata/android/lo-LA/changelogs/40103160.txt b/fastlane/metadata/android/lo-LA/changelogs/40103160.txt new file mode 100644 index 0000000000..a17f218463 --- /dev/null +++ b/fastlane/metadata/android/lo-LA/changelogs/40103160.txt @@ -0,0 +1,2 @@ +ການປ່ຽນແປງຫຼັກໃນສະບັບນີ້: ສົ່ງສະຖານທີ່ຂອງທ່ານໄປຫາຫ້ອງໃດກໍໄດ້. ແກ້ໄຂແບບສຳຫຼວດ. +ບັນທຶກການປ່ຽນແປງສະບັບເຕັມ: https://github.com/vector-im/element-android/releases/tag/v1.3.16 diff --git a/fastlane/metadata/android/lo-LA/changelogs/40103170.txt b/fastlane/metadata/android/lo-LA/changelogs/40103170.txt new file mode 100644 index 0000000000..b8337a19cc --- /dev/null +++ b/fastlane/metadata/android/lo-LA/changelogs/40103170.txt @@ -0,0 +1,2 @@ +ການປ່ຽນແປງຫຼັກໃນສະບັບນີ້: ສົ່ງສະຖານທີ່ຂອງທ່ານໄປຫາຫ້ອງໃດກໍໄດ້. ແກ້ໄຂແບບສຳຫຼວດ. +ບັນທຶກການປ່ຽນແປງສະບັບເຕັມ: https://github.com/vector-im/element-android/releases/tag/v1.3.17 diff --git a/fastlane/metadata/android/lo-LA/changelogs/40103180.txt b/fastlane/metadata/android/lo-LA/changelogs/40103180.txt new file mode 100644 index 0000000000..e6b65ff812 --- /dev/null +++ b/fastlane/metadata/android/lo-LA/changelogs/40103180.txt @@ -0,0 +1,2 @@ +ການປ່ຽນແປງຫຼັກໃນສະບັບນີ້: ສົ່ງສະຖານທີ່ຂອງທ່ານໄປຫາຫ້ອງໃດກໍໄດ້. ແກ້ໄຂແບບສຳຫຼວດ. +ບັນທຶກການປ່ຽນແປງສະບັບເຕັມ: https://github.com/vector-im/element-android/releases/tag/v1.3.18 diff --git a/fastlane/metadata/android/lo-LA/changelogs/40104000.txt b/fastlane/metadata/android/lo-LA/changelogs/40104000.txt new file mode 100644 index 0000000000..9d849da5b7 --- /dev/null +++ b/fastlane/metadata/android/lo-LA/changelogs/40104000.txt @@ -0,0 +1,2 @@ +ການປ່ຽນແປງຫຼັກໃນສະບັບນີ້: ການປະຕິບັດເບື້ອງຕົ້ນຂອງຂໍ້ຄວາມແບບກະທູ້. ຂໍ້ຄວາມຮູບແບບປຸມເປົ້າ. +ບັນທຶກການປ່ຽນແປງສະບັບເຕັມ: https://github.com/vector-im/element-android/releases/tag/v1.4.0 diff --git a/fastlane/metadata/android/lo-LA/changelogs/40104020.txt b/fastlane/metadata/android/lo-LA/changelogs/40104020.txt new file mode 100644 index 0000000000..358ca7fd39 --- /dev/null +++ b/fastlane/metadata/android/lo-LA/changelogs/40104020.txt @@ -0,0 +1,2 @@ +ການປ່ຽນແປງຫຼັກໃນສະບັບນີ້: ເພີ່ມການສະຫນັບສະຫນູນກັບ @room ແລະແບບສໍາຫຼວດທີ່ບໍ່ເປີດເຜີຍໃນບັນດາການປ່ຽນແປງເລັກນ້ອຍອື່ນໆ. +ບັນທຶກການປ່ຽນແປງສະບັບເຕັມ: https://github.com/vector-im/element-android/releases/tag/v1.4.2 diff --git a/fastlane/metadata/android/lo-LA/changelogs/40104040.txt b/fastlane/metadata/android/lo-LA/changelogs/40104040.txt new file mode 100644 index 0000000000..7bfaf76bf2 --- /dev/null +++ b/fastlane/metadata/android/lo-LA/changelogs/40104040.txt @@ -0,0 +1,2 @@ +ການປ່ຽນແປງຫຼັກໃນສະບັບນີ້: ຕົວສະແດງການພິມ ແລະປັບປັງ IU. ແກ້ໄຂບັກຫຼາຍບ່ອນ ແລະ ປັບປຸງຄວາມສະຖຽນ. +ບັນທຶກການປ່ຽນແປງສະບັບເຕັມ: https://github.com/vector-im/element-android/releases/tag/v1.4.4 diff --git a/fastlane/metadata/android/lo-LA/changelogs/40104060.txt b/fastlane/metadata/android/lo-LA/changelogs/40104060.txt new file mode 100644 index 0000000000..10ae599698 --- /dev/null +++ b/fastlane/metadata/android/lo-LA/changelogs/40104060.txt @@ -0,0 +1,2 @@ +ການປ່ຽນແປງຫຼັກໃນສະບັບນີ້: ທາມລາຍກະທູ້ ປະຈຸບັນເປັນແບບຖ່າຍທອດສົດ ແລະໄວຂຶ້ນ. ແກ້ໄຂບັກຫຼາຍບ່ອນ ແລະ ປັບປຸງຄວາມສະຖຽນ. +ບັນທຶກການປ່ຽນແປງສະບັບເຕັມ: https://github.com/vector-im/element-android/releases/tag/v1.4.6 diff --git a/fastlane/metadata/android/lo-LA/changelogs/40104070.txt b/fastlane/metadata/android/lo-LA/changelogs/40104070.txt new file mode 100644 index 0000000000..4ede801350 --- /dev/null +++ b/fastlane/metadata/android/lo-LA/changelogs/40104070.txt @@ -0,0 +1,2 @@ +ການປ່ຽນແປງຫຼັກໃນສະບັບນີ້: ແກ້ໄຂບັກຫຼາຍບ່ອນ ແລະ ປັບປຸງຄວາມສະຖຽນ. +ບັນທຶກການປ່ຽນແປງສະບັບເຕັມ: https://github.com/vector-im/element-android/releases/tag/v1.4.7 diff --git a/fastlane/metadata/android/lo-LA/changelogs/40104080.txt b/fastlane/metadata/android/lo-LA/changelogs/40104080.txt new file mode 100644 index 0000000000..5d7de22065 --- /dev/null +++ b/fastlane/metadata/android/lo-LA/changelogs/40104080.txt @@ -0,0 +1,2 @@ +ການປ່ຽນແປງຫຼັກໃນສະບັບນີ້: ເລື່ອນໃນຂໍ້ຄວາມສຽງ. ແກ້ໄຂບັກຫຼາຍບ່ອນ ແລະ ປັບປຸງຄວາມສະຖຽນ. +ບັນທຶກການປ່ຽນແປງສະບັບເຕັມ: https://github.com/vector-im/element-android/releases diff --git a/fastlane/metadata/android/lo-LA/changelogs/40104100.txt b/fastlane/metadata/android/lo-LA/changelogs/40104100.txt new file mode 100644 index 0000000000..5d7de22065 --- /dev/null +++ b/fastlane/metadata/android/lo-LA/changelogs/40104100.txt @@ -0,0 +1,2 @@ +ການປ່ຽນແປງຫຼັກໃນສະບັບນີ້: ເລື່ອນໃນຂໍ້ຄວາມສຽງ. ແກ້ໄຂບັກຫຼາຍບ່ອນ ແລະ ປັບປຸງຄວາມສະຖຽນ. +ບັນທຶກການປ່ຽນແປງສະບັບເຕັມ: https://github.com/vector-im/element-android/releases diff --git a/fastlane/metadata/android/lo-LA/changelogs/40104110.txt b/fastlane/metadata/android/lo-LA/changelogs/40104110.txt new file mode 100644 index 0000000000..edc025cb39 --- /dev/null +++ b/fastlane/metadata/android/lo-LA/changelogs/40104110.txt @@ -0,0 +1,2 @@ +ການປ່ຽນແປງຫຼັກໃນສະບັບນີ້: ການແກ້ໄຂ bug ຕ່າງໆແລະການປັບປຸງຄວາມສະຖຽນ. +ບັນທຶກການປ່ຽນແປງສະບັບເຕັມ: https://github.com/vector-im/element-android/releases diff --git a/fastlane/metadata/android/lo-LA/changelogs/40104120.txt b/fastlane/metadata/android/lo-LA/changelogs/40104120.txt new file mode 100644 index 0000000000..36c6d678f7 --- /dev/null +++ b/fastlane/metadata/android/lo-LA/changelogs/40104120.txt @@ -0,0 +1,2 @@ +ການປ່ຽນແປງຫຼັກໃນສະບັບນີ້: ໃຫ້ຜູ້ໃຊ້ສາມາດສະແດງຕົວເປັນ offline ແລະສາມາດຫຼິ້ນສຽງໄດ້ສຳລັບການແນບສຽງ +ບັນທຶກການປ່ຽນແປງສະບັບເຕັມ: https://github.com/vector-im/element-android/releases diff --git a/fastlane/metadata/android/lo-LA/changelogs/40104130.txt b/fastlane/metadata/android/lo-LA/changelogs/40104130.txt new file mode 100644 index 0000000000..36c6d678f7 --- /dev/null +++ b/fastlane/metadata/android/lo-LA/changelogs/40104130.txt @@ -0,0 +1,2 @@ +ການປ່ຽນແປງຫຼັກໃນສະບັບນີ້: ໃຫ້ຜູ້ໃຊ້ສາມາດສະແດງຕົວເປັນ offline ແລະສາມາດຫຼິ້ນສຽງໄດ້ສຳລັບການແນບສຽງ +ບັນທຶກການປ່ຽນແປງສະບັບເຕັມ: https://github.com/vector-im/element-android/releases diff --git a/fastlane/metadata/android/lo-LA/full_description.txt b/fastlane/metadata/android/lo-LA/full_description.txt new file mode 100644 index 0000000000..094c1b57f5 --- /dev/null +++ b/fastlane/metadata/android/lo-LA/full_description.txt @@ -0,0 +1,42 @@ +ລະບົບແຊັດນີ້ ມີຄວາມປອດໄພ ແລະເປັນແອັບທີ່ເຮັດຊ່ວຍສົ່ງເສີ່ມການເຮັດວຽກຮ່ວມກັນຢ່າງມີປະສິດທິຜົນໝາະສົມສຳລັບການສົນທະນາກຸ່ມໃນຂະນະທີ່ເຮັດວຽກທາງໄກ. ແອັບສົນທະນານີ້ໃຊ້ການເຂົ້າລະຫັດແບບຕົ້ນທາງຈົນຈົບເພື່ອໃຫ້ການປະຊຸມວິດີໂອທີ່ມີປະສິດທິພາບ, ການແບ່ງປັນໄຟລ໌ ແລະການໂທແບບສຽງ. + +ຄຸນສົມບັດຂອງລະບົບລວມມີ: +- ເຄື່ອງມືການສື່ສານອອນໄລນ໌ແບບລຳ້ໜ້າ +- ຂໍ້ຄວາມມີການເຂົ້າລະຫັດຢ່າງເຕັມສ່ວນເພື່ອໃຫ້ການສື່ສານຂອງອົງກອນມີຄວາມປອດໄພ, ເຖິງແມ່ນວ່າຈະເຮັດວຽກຈາກທາງໄກ +- ລະບົບແຊັດແບບແບ່ງກະຈາຍການຄຸ້ມຄອງໂດຍນຳໃຊ້ນະວັດຕະກຳເປີດຂອງລະບົບ Matrix +- ການແບ່ງປັນໄຟລ໌ຢ່າງປອດໄພກັບຂໍ້ມູນທີ່ເຂົ້າລະຫັດໄວ້ໃນຂະນະທີ່ຈັດການໂຄງການ +- ວິດີໂອສົນທະນາກັບ Voice over IP ແລະການແບ່ງປັນຫນ້າຈໍ +- ການເຊື່ອມໂຍງງ່າຍດາຍກັບເຄື່ອງມືການຮ່ວມມືອອນໄລນ໌ຂອງທ່ານ, ເຄື່ອງມືການຄຸ້ມຄອງໂຄງການ, ການບໍລິການ VoIP ແລະຂໍ້ຄວາມອື່ນໆຂອງທີມ + +ລະບົບນີ້ແມ່ນແຕກຕ່າງກັນຢ່າງສິ້ນເຊີງຈາກແອັບຯສົ່ງຂໍ້ຄວາມ ແລະແອັບສົ່ງເສີມການຮ່ວມມືອື່ນໆ. ມັນດໍາເນີນການຢູ່ໃນ Matrix, ເຄືອຂ່າຍເປີດສໍາລັບການສົ່ງຂໍ້ຄວາມທີ່ປອດໄພແລະການສື່ສານແບບກະຈາຍການຄຸ້ມຄອງ. ທ່ານສາມາດຕັ້ງສະຖານີດ້ວຍຕົນເອງເພື່ອໃຫ້ຜູ້ໃຊ້ເປັນເຈົ້າຂອງສູງສຸດແລະຄຸ້ມຄອງຂໍ້ມູນແລະຂໍ້ຄວາມຂອງຕົນເອງ. + +ຄວາມເປັນສ່ວນຕົວ ແລະຂໍ້ຄວາມເຂົ້າລະຫັດ +ລະບົບຈະປົກປ້ອງທ່ານຈາກການໂຄສະນາທີ່ບໍ່ຕ້ອງການ, ການຂຸດຄົ້ນຂໍ້ມູນແລະການສ້າງເງື່ອນໄຂປິດລ້ອມ. ລະບົບຍັງຮັບປະກັນຂໍ້ມູນທັງຫມົດຂອງທ່ານ, ການໂທວິດີໂອແບບຫນຶ່ງຕໍ່ຫນຶ່ງແລະການສື່ສານສຽງໂດຍຜ່ານການເຂົ້າລະຫັດແບບຕົ້ນທາງຫາປາຍທາງ ແລະການກວດສອບອຸປະກອນກ່ອນເຊື່ອມຕໍ່. + +ລະບົບເຮົາໃຫ້ທ່ານຄວບຄຸມຄວາມເປັນສ່ວນຕົວຂອງທ່ານໃນຂະນະທີ່ອະນຸຍາດໃຫ້ທ່ານຕິດຕໍ່ສື່ສານຢ່າງປອດໄພກັບທຸກຄົນໃນເຄືອຂ່າຍ Matrix, ຫຼືເຄື່ອງມືທີ່ທ່ານຄຸ້ນເຄີຍເຊັ່ນ Whatsapp, Slack, Facebook. + +ທ່ານສາມາດສ້າງສະຖານີຂອງຕົນເອງ +ເພື່ອໃຫ້ສາມາດຄວບຄຸມຂໍ້ມູນແລະການສົນທະນາທີ່ລະອຽດອ່ອນຂອງທ່ານຫຼາຍຂຶ້ນ, ທ່ານສາມາດຕິດຕັ້ງສະຖານີສື່ສານຂອງຕົນເອງຫຼືທ່ານສາມາດເລືອກຕິດຕັ້ງໄວ້ກັບເຄືອຂ່າຍ Matrix - ມາດຕະຖານສໍາລັບລະບົບເປີດ, ການສື່ສານແບບກະຈາຍການຄຸ້ມຄອງ. ລະບົບພວກເຮົາໃຫ້ທ່ານມີຄວາມເປັນສ່ວນຕົວ, ໄດ້ຕາມມາດຕະຖານຄວາມປອດໄພແລະມີຄວາມຍືດຫຍຸ່ນໃນການເຊື່ອມໂຍງ. + +ເປັນເຈົ້າຂອງຂໍ້ມູນຂອງທ່ານເອງ +ທ່ານສາມາດເລືອກໄດ້ວ່າຈະເກັບຂໍ້ມູນ ແລະຂໍ້ຄວາມຂອງທ່ານຢູ່ໃສ. ໂດຍບໍ່ມີຄວາມສ່ຽງຕໍ່ການຂຸດຄົ້ນຂໍ້ມູນຫຼືການເຂົ້າເຖິງຈາກພາກສ່ວນທີສາມ. + +ລະບົບພວກເຮົາເຮັດໃຫ້ທ່ານສາມາດຄວບຄຸມດ້ວຍວິທີຕ່າງໆ: +1. ຮັບບັນຊີຟຣີໃນເຊີບເວີສາທາລະນະ matrix.org ທີ່ໂຮສໂດຍຜູ້ພັດທະນາ Matrix, ຫຼືເລືອກຈາກເຄື່ອງແມ່ຂ່າຍສາທາລະນະຫຼາຍພັນເຊີບເວີທີ່ໂຮສໂດຍອາສາສະໝັກ. +2. ຈັດການບັນຊີຂອງທ່ານເອງໂດຍການແລ່ນເຊີບເວີໃນໂຄງລ່າງພື້ນຖານ IT ຂອງທ່ານເອງ +3. ລົງທະບຽນສໍາລັບບັນຊີຢູ່ໃນເຄື່ອງແມ່ຂ່າຍທີ່ກໍາຫນົດເອງໂດຍພຽງແຕ່ສະຫມັກກັບ Element Matrix Services hosting platform + +ສົ່ງຂໍ້ຄວາມ ແລະການຮ່ວມມືກັບລະບົບອື່ນໆທີ່ທ່ານຄຸ້ນເຄີຍ +ທ່ານສາມາດສົນທະນາກັບທຸກຄົນໃນເຄືອຂ່າຍ Matrix, ບໍ່ວ່າເຂົາເຈົ້າກໍາລັງໃຊ້ Element, ແອັບ Matrix ອື່ນ ຫຼືເຖິງແມ່ນວ່າເຂົາເຈົ້າກຳລັງໃຊ້ແອັບສົ່ງຂໍ້ຄວາມອື່ນຢູ່. + +ປອດໄພທີ່ສຸດ +ການເຂົ້າລະຫັດຈາກຕົ້ນທາງຫາປາຍທາງ (ມີພຽງແຕ່ຜູ້ທີ່ຢູ່ໃນການສົນທະນາສາມາດຖອດລະຫັດຂໍ້ຄວາມໄດ້), ແລະການຢັ້ງຢືນອຸປະກອນໂດຍການລົງນາມຂ້າມອຸປະກອນ. + +ການສື່ສານແລະການເຊື່ອມໂຍງທີ່ຄົບຖ້ວນສົມບູນ +ການສົ່ງຂໍ້ຄວາມ, ການໂທສຽງ ແລະວິດີໂອ, ການແບ່ງປັນໄຟລ໌, ການແບ່ງປັນໜ້າຈໍ ແລະການເຊື່ອມໂຍງທັງໝົດ, ບັອດ ແລະວິດເຈັດ. ສ້າງຫ້ອງ, ຊຸມຊົນ, ຕິດຕໍ່ພົວພັນແລະເຮັດສິ່ງຕ່າງໆ. + +ສືບຕໍ່ຈາກການສົນທະນາລ້າສຸດ +ທ່ານສາມາດຕິດຕໍ່ພົວພັນໄດ້ທຸກບ່ອນທີ່ຕ້ອງການ ເຊິ່ງປະຫວັດການສົນທະນາຈະຖືກອັບເດດໄປຫາບັນດາອຸປະກອນຕ່າງໆຂອງທ່ານ ລວມເຖິງໃນເວັບໄຊຕ໌ທີ່ https://app.element.io + +ເຕັກໂນໂລຊີໂອເພິນຊອດ +Element ເປັນໂຄງການເຕັກໂນໂລຊີໂອເພິນຊອດ, ເປັນເຈົ້າພາບໂດຍ GitHub. ກະລຸນາລາຍງານຂໍ້ບົກພ່ອງ ແລະ/ຫຼື ປະກອບສ່ວນເຂົ້າໃນການພັດທະນາຂອງລະບົບຢູ່ https://github.com/vector-im/element-android diff --git a/fastlane/metadata/android/lo-LA/short_description.txt b/fastlane/metadata/android/lo-LA/short_description.txt new file mode 100644 index 0000000000..625b078915 --- /dev/null +++ b/fastlane/metadata/android/lo-LA/short_description.txt @@ -0,0 +1 @@ +ສົ່ງຂໍ້ຄວາມກຸ່ມ - ສົ່ງຂໍ້ຄວາມທີ່ເຂົ້າລະຫັດ, ສົນທະນາກຸ່ມ ແລະໂທວິດີໂອ diff --git a/fastlane/metadata/android/lo-LA/title.txt b/fastlane/metadata/android/lo-LA/title.txt new file mode 100644 index 0000000000..a9054d803b --- /dev/null +++ b/fastlane/metadata/android/lo-LA/title.txt @@ -0,0 +1 @@ +ລະບົບສື່ສານທີ່ມີຄວາມປອດໄພສູງ diff --git a/fastlane/metadata/android/pl-PL/changelogs/40103050.txt b/fastlane/metadata/android/pl-PL/changelogs/40103050.txt new file mode 100644 index 0000000000..b81d5ef037 --- /dev/null +++ b/fastlane/metadata/android/pl-PL/changelogs/40103050.txt @@ -0,0 +1,2 @@ +Główne zmiany w tej wersji: Dodanie obsługi obecności, dla pokoju wiadomości bezpośrednich (uwaga: obecność jest wyłączona na matrix.org). Dodaje ponownie obsługę Androida Auto. + Pełna lista zmian: https://github.com/vector-im/element-android/releases/tag/v1.3.5 diff --git a/fastlane/metadata/android/pl-PL/changelogs/40103060.txt b/fastlane/metadata/android/pl-PL/changelogs/40103060.txt new file mode 100644 index 0000000000..9dc1e9e297 --- /dev/null +++ b/fastlane/metadata/android/pl-PL/changelogs/40103060.txt @@ -0,0 +1,2 @@ +Główne zmiany w tej wersji: Dodanie obsługi obecności, dla pokoju wiadomości bezpośrednich (uwaga: obecność jest wyłączona na matrix.org). Dodano ponownie obsługę Android Auto. +Pełna lista zmian: https://github.com/vector-im/element-android/releases/tag/v1.3.6 diff --git a/fastlane/metadata/android/pl-PL/changelogs/40103070.txt b/fastlane/metadata/android/pl-PL/changelogs/40103070.txt index 2cb20a9570..08d9d2193e 100644 --- a/fastlane/metadata/android/pl-PL/changelogs/40103070.txt +++ b/fastlane/metadata/android/pl-PL/changelogs/40103070.txt @@ -1,2 +1,2 @@ -Główne zmiany w tej wersji: Poprawki błędów dotyczące głównie powiadomień -Pełny changelog: https://github.com/vector-im/element-android/releases/tag/v1.3.7 +Główne zmiany w tej wersji: Poprawki błędów dotyczące głównie powiadomień. +Pełna lista zmian: https://github.com/vector-im/element-android/releases/tag/v1.3.7-RC2 diff --git a/fastlane/metadata/android/pl-PL/changelogs/40103080.txt b/fastlane/metadata/android/pl-PL/changelogs/40103080.txt new file mode 100644 index 0000000000..c850479f59 --- /dev/null +++ b/fastlane/metadata/android/pl-PL/changelogs/40103080.txt @@ -0,0 +1,2 @@ +Główne zmiany w tej wersji: Poprawki błędów! +Pełna lista zmian: https://github.com/vector-im/element-android/releases/tag/v1.3.8 diff --git a/fastlane/metadata/android/pl-PL/changelogs/40103090.txt b/fastlane/metadata/android/pl-PL/changelogs/40103090.txt new file mode 100644 index 0000000000..13e1aed51e --- /dev/null +++ b/fastlane/metadata/android/pl-PL/changelogs/40103090.txt @@ -0,0 +1,2 @@ +Główne zmiany w tej wersji: Dodano obsługę wersji roboczej wiadomości głosowych. Wiele poprawek! +Pełna lista zmian: https://github.com/vector-im/element-android/releases/tag/v1.3.9 diff --git a/fastlane/metadata/android/pl-PL/changelogs/40103100.txt b/fastlane/metadata/android/pl-PL/changelogs/40103100.txt new file mode 100644 index 0000000000..406f07f70f --- /dev/null +++ b/fastlane/metadata/android/pl-PL/changelogs/40103100.txt @@ -0,0 +1,2 @@ +Główne zmiany w tej wersji: Dodano obsługę ankiet (w laboratoriach). Nowy projekt podglądu adresu URL. +Pełna lista zmian: https://github.com/vector-im/element-android/releases/tag/v1.3.10 diff --git a/fastlane/metadata/android/pl-PL/changelogs/40103110.txt b/fastlane/metadata/android/pl-PL/changelogs/40103110.txt new file mode 100644 index 0000000000..f9b38673c7 --- /dev/null +++ b/fastlane/metadata/android/pl-PL/changelogs/40103110.txt @@ -0,0 +1,2 @@ +Główne zmiany w tej wersji: Poprawki błędów! +Pełna lista zmian: https://github.com/vector-im/element-android/releases/tag/v1.3.11 diff --git a/fastlane/metadata/android/pl-PL/changelogs/40103120.txt b/fastlane/metadata/android/pl-PL/changelogs/40103120.txt new file mode 100644 index 0000000000..db6c1c86bd --- /dev/null +++ b/fastlane/metadata/android/pl-PL/changelogs/40103120.txt @@ -0,0 +1,2 @@ +Główne zmiany w tej wersji: Poprawki błędów! +Pełna lista zmian: https://github.com/vector-im/element-android/releases/tag/v1.3.12 diff --git a/fastlane/metadata/android/pl-PL/changelogs/40103130.txt b/fastlane/metadata/android/pl-PL/changelogs/40103130.txt new file mode 100644 index 0000000000..affd085177 --- /dev/null +++ b/fastlane/metadata/android/pl-PL/changelogs/40103130.txt @@ -0,0 +1,2 @@ +Główne zmiany w tej wersji: Pierwsza zmiana na ekranach rejestracji, w tym możliwość włączenia Analytics. Dodano obsługę dla wydarzeń z dodatkiem matematyki w laboratoriach. +Pełna lista zmian: https://github.com/vector-im/element-android/releases/tag/v1.3.13 diff --git a/fastlane/metadata/android/pl-PL/changelogs/40103140.txt b/fastlane/metadata/android/pl-PL/changelogs/40103140.txt new file mode 100644 index 0000000000..0727d3b4ff --- /dev/null +++ b/fastlane/metadata/android/pl-PL/changelogs/40103140.txt @@ -0,0 +1,2 @@ +Główne zmiany w tej wersji: Pierwsza zmiana na ekranach rejestracji, w tym możliwość włączenia Analytics. Dodano obsługę dla wydarzeń z dodatkiem matematyki w laboratoriach. +Pełna lista zmian: https://github.com/vector-im/element-android/releases/tag/v1.3.14 diff --git a/fastlane/metadata/android/pl-PL/changelogs/40103150.txt b/fastlane/metadata/android/pl-PL/changelogs/40103150.txt new file mode 100644 index 0000000000..48d6d0feb3 --- /dev/null +++ b/fastlane/metadata/android/pl-PL/changelogs/40103150.txt @@ -0,0 +1,2 @@ +Główne zmiany w tej wersji: Pierwsza zmiana na ekranach rejestracji, w tym możliwość włączenia Analytics. Dodano obsługę dla wydarzeń z dodatkiem matematyki w laboratoriach. +Pełna lista zmian: https://github.com/vector-im/element-android/releases/tag/v1.3.15 diff --git a/fastlane/metadata/android/pl-PL/changelogs/40103160.txt b/fastlane/metadata/android/pl-PL/changelogs/40103160.txt new file mode 100644 index 0000000000..df66e3042a --- /dev/null +++ b/fastlane/metadata/android/pl-PL/changelogs/40103160.txt @@ -0,0 +1,2 @@ +Główne zmiany w tej wersji: wyślij swoją lokalizację do dowolnego pokoju. Edytuj ankietę. +Pełna lista zmian: https://github.com/vector-im/element-android/releases/tag/v1.3.16 diff --git a/fastlane/metadata/android/pl-PL/changelogs/40103170.txt b/fastlane/metadata/android/pl-PL/changelogs/40103170.txt new file mode 100644 index 0000000000..77d40898a6 --- /dev/null +++ b/fastlane/metadata/android/pl-PL/changelogs/40103170.txt @@ -0,0 +1,2 @@ +Główne zmiany w tej wersji: wyślij swoją lokalizację do dowolnego pokoju. Edytuj ankietę. +Pełna lista zmian: https://github.com/vector-im/element-android/releases/tag/v1.3.17 diff --git a/fastlane/metadata/android/pl-PL/changelogs/40103180.txt b/fastlane/metadata/android/pl-PL/changelogs/40103180.txt new file mode 100644 index 0000000000..c0f671a58e --- /dev/null +++ b/fastlane/metadata/android/pl-PL/changelogs/40103180.txt @@ -0,0 +1,2 @@ +Główne zmiany w tej wersji: wyślij swoją lokalizację do dowolnego pokoju. Edytuj ankietę. +Pełna lista zmian: https://github.com/vector-im/element-android/releases/tag/v1.3.18 diff --git a/fastlane/metadata/android/pl-PL/changelogs/40104000.txt b/fastlane/metadata/android/pl-PL/changelogs/40104000.txt new file mode 100644 index 0000000000..f9f4c80cf9 --- /dev/null +++ b/fastlane/metadata/android/pl-PL/changelogs/40104000.txt @@ -0,0 +1,2 @@ +Główne zmiany w tej wersji: Wstępna implementacja komunikatów wątków. Bąbelki wiadomości. +Pełna lista zmian: https://github.com/vector-im/element-android/releases/tag/v1.4.0 diff --git a/fastlane/metadata/android/pl-PL/changelogs/40104020.txt b/fastlane/metadata/android/pl-PL/changelogs/40104020.txt new file mode 100644 index 0000000000..190427d548 --- /dev/null +++ b/fastlane/metadata/android/pl-PL/changelogs/40104020.txt @@ -0,0 +1,2 @@ +Główne zmiany w tej wersji: dodanie obsługi @room i nieujawnionych ankiet, i wiele innych drobnych zmian. +Pełna lista zmian: https://github.com/vector-im/element-android/releases/tag/v1.4.2 diff --git a/fastlane/metadata/android/pl-PL/changelogs/40104040.txt b/fastlane/metadata/android/pl-PL/changelogs/40104040.txt new file mode 100644 index 0000000000..9894e54c03 --- /dev/null +++ b/fastlane/metadata/android/pl-PL/changelogs/40104040.txt @@ -0,0 +1,2 @@ +Główne zmiany w tej wersji: aktualizacje interfejsu użytkownika wskaźnika pisania. Różne poprawki błędów i ulepszenia stabilności. +Pełna lista zmian: https://github.com/vector-im/element-android/releases/tag/v1.4.4 diff --git a/fastlane/metadata/android/pl-PL/changelogs/40104060.txt b/fastlane/metadata/android/pl-PL/changelogs/40104060.txt new file mode 100644 index 0000000000..216842ada0 --- /dev/null +++ b/fastlane/metadata/android/pl-PL/changelogs/40104060.txt @@ -0,0 +1,2 @@ +Główne zmiany w tej wersji: Oś czasu wątku jest teraz aktywna i szybsza. Różne poprawki błędów i ulepszenia stabilności. +Pełna lista zmian: https://github.com/vector-im/element-android/releases/tag/v1.4.6 diff --git a/fastlane/metadata/android/pl-PL/changelogs/40104070.txt b/fastlane/metadata/android/pl-PL/changelogs/40104070.txt new file mode 100644 index 0000000000..91c13d3d79 --- /dev/null +++ b/fastlane/metadata/android/pl-PL/changelogs/40104070.txt @@ -0,0 +1,2 @@ +Główne zmiany w tej wersji: Różne poprawki błędów i ulepszenia stabilności. +Pełna lista zmian: https://github.com/vector-im/element-android/releases/tag/v1.4.7 diff --git a/fastlane/metadata/android/pl-PL/changelogs/40104080.txt b/fastlane/metadata/android/pl-PL/changelogs/40104080.txt new file mode 100644 index 0000000000..5286f40de5 --- /dev/null +++ b/fastlane/metadata/android/pl-PL/changelogs/40104080.txt @@ -0,0 +1,2 @@ +Główne zmiany w tej wersji: Oś czasu wątku jest teraz aktywna i szybsza. Różne poprawki błędów i ulepszenia stabilności. +Pełna lista zmian: https://github.com/vector-im/element-android/releases diff --git a/fastlane/metadata/android/pl-PL/changelogs/40104100.txt b/fastlane/metadata/android/pl-PL/changelogs/40104100.txt new file mode 100644 index 0000000000..3d1efbc1d2 --- /dev/null +++ b/fastlane/metadata/android/pl-PL/changelogs/40104100.txt @@ -0,0 +1,2 @@ +Główne zmiany w tej wersji: Przewijanie w wiadomości głosowej. Różne poprawki błędów i ulepszenia stabilności. + Pełna lista zmian: https://github.com/vector-im/element-android/releases diff --git a/fastlane/metadata/android/pl-PL/changelogs/40104110.txt b/fastlane/metadata/android/pl-PL/changelogs/40104110.txt new file mode 100644 index 0000000000..6ae86f3c9b --- /dev/null +++ b/fastlane/metadata/android/pl-PL/changelogs/40104110.txt @@ -0,0 +1,2 @@ +Główne zmiany w tej wersji: Różne poprawki błędów i ulepszenia stabilności. + Pełna lista zmian: https://github.com/vector-im/element-android/releases diff --git a/fastlane/metadata/android/pl-PL/changelogs/40104120.txt b/fastlane/metadata/android/pl-PL/changelogs/40104120.txt new file mode 100644 index 0000000000..bd995ad5e5 --- /dev/null +++ b/fastlane/metadata/android/pl-PL/changelogs/40104120.txt @@ -0,0 +1,2 @@ +Główne zmiany w tej wersji: Pozwala użytkownikom pojawiać się w trybie offline i dodaje odtwarzacz audio do załączników audio +Pełna lista zmian: https://github.com/vector-im/element-android/releases diff --git a/fastlane/metadata/android/pl-PL/changelogs/40104130.txt b/fastlane/metadata/android/pl-PL/changelogs/40104130.txt new file mode 100644 index 0000000000..bd995ad5e5 --- /dev/null +++ b/fastlane/metadata/android/pl-PL/changelogs/40104130.txt @@ -0,0 +1,2 @@ +Główne zmiany w tej wersji: Pozwala użytkownikom pojawiać się w trybie offline i dodaje odtwarzacz audio do załączników audio +Pełna lista zmian: https://github.com/vector-im/element-android/releases diff --git a/fastlane/metadata/android/pl-PL/title.txt b/fastlane/metadata/android/pl-PL/title.txt index 907f907f99..df4d9b71f4 100644 --- a/fastlane/metadata/android/pl-PL/title.txt +++ b/fastlane/metadata/android/pl-PL/title.txt @@ -1 +1 @@ -Element +Element - komunikator diff --git a/fastlane/metadata/android/pt-BR/changelogs/40104060.txt b/fastlane/metadata/android/pt-BR/changelogs/40104060.txt new file mode 100644 index 0000000000..17d8225c0d --- /dev/null +++ b/fastlane/metadata/android/pt-BR/changelogs/40104060.txt @@ -0,0 +1,2 @@ +Principais mudanças nesta versão: Timeline de Thread estão agora ao vivo e mais rápidas. Vários consertos de bugs e melhorias de estabilidade. +Changelog completo: https://github.com/vector-im/element-android/releases/tag/v1.4.6 diff --git a/fastlane/metadata/android/pt-BR/changelogs/40104070.txt b/fastlane/metadata/android/pt-BR/changelogs/40104070.txt new file mode 100644 index 0000000000..fad9bfe739 --- /dev/null +++ b/fastlane/metadata/android/pt-BR/changelogs/40104070.txt @@ -0,0 +1,2 @@ +Principais mudanças nesta versão: Vários consertos de bugs e melhorias de estabilidade. +Changelog completo: https://github.com/vector-im/element-android/releases/tag/v1.4.7 diff --git a/fastlane/metadata/android/pt-BR/changelogs/40104080.txt b/fastlane/metadata/android/pt-BR/changelogs/40104080.txt new file mode 100644 index 0000000000..ca2b54f2f2 --- /dev/null +++ b/fastlane/metadata/android/pt-BR/changelogs/40104080.txt @@ -0,0 +1,2 @@ +Principais mudanças nesta versão: Timeline de Thread estão agora ao vivo e mais rápidas. Vários consertos de bugs e melhorias de estabilidade. +Changelog completo: https://github.com/vector-im/element-android/releases diff --git a/fastlane/metadata/android/pt-BR/changelogs/40104100.txt b/fastlane/metadata/android/pt-BR/changelogs/40104100.txt new file mode 100644 index 0000000000..eb746aca2b --- /dev/null +++ b/fastlane/metadata/android/pt-BR/changelogs/40104100.txt @@ -0,0 +1,2 @@ +Principais mudanças nesta versão: Rolar em mensagem de voz. Vários consertos de bugs e melhorias de estabilidade. +Changelog completo: https://github.com/vector-im/element-android/releases diff --git a/fastlane/metadata/android/pt-BR/changelogs/40104110.txt b/fastlane/metadata/android/pt-BR/changelogs/40104110.txt new file mode 100644 index 0000000000..6e11e92579 --- /dev/null +++ b/fastlane/metadata/android/pt-BR/changelogs/40104110.txt @@ -0,0 +1,2 @@ +Principais mudanças nesta versão: Vários consertos de bugs e melhorias de estabilidade. +Changelog completo: https://github.com/vector-im/element-android/releases diff --git a/fastlane/metadata/android/pt-BR/changelogs/40104120.txt b/fastlane/metadata/android/pt-BR/changelogs/40104120.txt new file mode 100644 index 0000000000..f77d426d99 --- /dev/null +++ b/fastlane/metadata/android/pt-BR/changelogs/40104120.txt @@ -0,0 +1,2 @@ +Principais mudanças nesta versão: Permite usuárias(os) aparecer offline e adiciona um tocador de áudio para anexos de áudio +Changelog completo: https://github.com/vector-im/element-android/releases diff --git a/fastlane/metadata/android/pt-BR/changelogs/40104130.txt b/fastlane/metadata/android/pt-BR/changelogs/40104130.txt new file mode 100644 index 0000000000..f77d426d99 --- /dev/null +++ b/fastlane/metadata/android/pt-BR/changelogs/40104130.txt @@ -0,0 +1,2 @@ +Principais mudanças nesta versão: Permite usuárias(os) aparecer offline e adiciona um tocador de áudio para anexos de áudio +Changelog completo: https://github.com/vector-im/element-android/releases diff --git a/fastlane/metadata/android/sk/changelogs/40104060.txt b/fastlane/metadata/android/sk/changelogs/40104060.txt new file mode 100644 index 0000000000..53e192fe7c --- /dev/null +++ b/fastlane/metadata/android/sk/changelogs/40104060.txt @@ -0,0 +1,2 @@ +Hlavné zmeny v tejto verzii: Vlákna na časovej osi sú teraz živé a rýchlejšie. Rôzne opravy chýb a vylepšenia stability. +Úplný zoznam zmien: https://github.com/vector-im/element-android/releases/tag/v1.4.6 diff --git a/fastlane/metadata/android/sk/changelogs/40104070.txt b/fastlane/metadata/android/sk/changelogs/40104070.txt new file mode 100644 index 0000000000..1e056cf32f --- /dev/null +++ b/fastlane/metadata/android/sk/changelogs/40104070.txt @@ -0,0 +1,2 @@ +Hlavné zmeny v tejto verzii: Rôzne opravy chýb a vylepšenia stability. +Úplný zoznam zmien: https://github.com/vector-im/element-android/releases/tag/v1.4.7 diff --git a/fastlane/metadata/android/sk/changelogs/40104080.txt b/fastlane/metadata/android/sk/changelogs/40104080.txt new file mode 100644 index 0000000000..7f42250fb9 --- /dev/null +++ b/fastlane/metadata/android/sk/changelogs/40104080.txt @@ -0,0 +1,2 @@ +Hlavné zmeny v tejto verzii: Vlákna na časovej osi sú teraz živé a rýchlejšie. Rôzne opravy chýb a vylepšenia stability. +Úplný zoznam zmien: https://github.com/vector-im/element-android/releases diff --git a/fastlane/metadata/android/sk/changelogs/40104100.txt b/fastlane/metadata/android/sk/changelogs/40104100.txt new file mode 100644 index 0000000000..b96a539364 --- /dev/null +++ b/fastlane/metadata/android/sk/changelogs/40104100.txt @@ -0,0 +1,2 @@ +Hlavné zmeny v tejto verzii: Posúvanie v hlasovej správe. Rôzne opravy chýb a vylepšenia stability. +Úplný zoznam zmien: https://github.com/vector-im/element-android/releases diff --git a/fastlane/metadata/android/sk/changelogs/40104110.txt b/fastlane/metadata/android/sk/changelogs/40104110.txt new file mode 100644 index 0000000000..50670f18c2 --- /dev/null +++ b/fastlane/metadata/android/sk/changelogs/40104110.txt @@ -0,0 +1,2 @@ +Hlavné zmeny v tejto verzii: Rôzne opravy chýb a vylepšenia stability. +Úplný zoznam zmien: https://github.com/vector-im/element-android/releases diff --git a/fastlane/metadata/android/sk/changelogs/40104120.txt b/fastlane/metadata/android/sk/changelogs/40104120.txt new file mode 100644 index 0000000000..2279ddc574 --- /dev/null +++ b/fastlane/metadata/android/sk/changelogs/40104120.txt @@ -0,0 +1,2 @@ +Hlavné zmeny v tejto verzii: Umožňuje používateľom zobrazovať sa v režime offline a pridáva zvukový prehrávač pre zvukové prílohy +Úplný zoznam zmien: https://github.com/vector-im/element-android/releases diff --git a/fastlane/metadata/android/sk/changelogs/40104130.txt b/fastlane/metadata/android/sk/changelogs/40104130.txt new file mode 100644 index 0000000000..2279ddc574 --- /dev/null +++ b/fastlane/metadata/android/sk/changelogs/40104130.txt @@ -0,0 +1,2 @@ +Hlavné zmeny v tejto verzii: Umožňuje používateľom zobrazovať sa v režime offline a pridáva zvukový prehrávač pre zvukové prílohy +Úplný zoznam zmien: https://github.com/vector-im/element-android/releases diff --git a/fastlane/metadata/android/sq/changelogs/40104060.txt b/fastlane/metadata/android/sq/changelogs/40104060.txt new file mode 100644 index 0000000000..48cbd2150c --- /dev/null +++ b/fastlane/metadata/android/sq/changelogs/40104060.txt @@ -0,0 +1,2 @@ +Ndryshimet kryesore në këtë version: Vijat kohore të rrjedhave tani janë në punë dhe më të shpejta. Ndreqje të metash të ndryshme dhe përmirësime qëndrueshmërie. +Regjistër i plotë ndryshimesh: https://github.com/vector-im/element-android/releases/tag/v1.4.6 diff --git a/fastlane/metadata/android/sq/changelogs/40104070.txt b/fastlane/metadata/android/sq/changelogs/40104070.txt new file mode 100644 index 0000000000..ad487c57ee --- /dev/null +++ b/fastlane/metadata/android/sq/changelogs/40104070.txt @@ -0,0 +1,2 @@ +Ndryshimet kryesore në këtë version: Ndreqje të metash të ndryshme dhe përmirësime qëndrueshmërie. +Regjistër i plotë ndryshimesh: https://github.com/vector-im/element-android/releases/tag/v1.4.7 diff --git a/fastlane/metadata/android/sq/changelogs/40104080.txt b/fastlane/metadata/android/sq/changelogs/40104080.txt new file mode 100644 index 0000000000..49156d0c96 --- /dev/null +++ b/fastlane/metadata/android/sq/changelogs/40104080.txt @@ -0,0 +1,2 @@ +Ndryshimet kryesore në këtë version: Vijat kohore të rrjedhave tani janë në punë dhe më të shpejta. Ndreqje të metash të ndryshme dhe përmirësime qëndrueshmërie. +Regjistër i plotë ndryshimesh: https://github.com/vector-im/element-android/releases diff --git a/fastlane/metadata/android/sq/changelogs/40104100.txt b/fastlane/metadata/android/sq/changelogs/40104100.txt new file mode 100644 index 0000000000..c45c2b9a33 --- /dev/null +++ b/fastlane/metadata/android/sq/changelogs/40104100.txt @@ -0,0 +1,2 @@ +Ndryshimet kryesore në këtë version: Rrëshqitje në mesazh zanor. Ndreqje të metash të ndryshme dhe përmirësime qëndrueshmërie. +Regjistër i plotë ndryshimesh: https://github.com/vector-im/element-android/releases diff --git a/fastlane/metadata/android/sq/changelogs/40104110.txt b/fastlane/metadata/android/sq/changelogs/40104110.txt new file mode 100644 index 0000000000..df9b377adb --- /dev/null +++ b/fastlane/metadata/android/sq/changelogs/40104110.txt @@ -0,0 +1,2 @@ +Ndryshimet kryesore në këtë version: Ndreqje të metash të ndryshme dhe përmirësime qëndrueshmërie. +Regjistër i plotë ndryshimesh: https://github.com/vector-im/element-android/releases diff --git a/fastlane/metadata/android/sv-SE/changelogs/40104060.txt b/fastlane/metadata/android/sv-SE/changelogs/40104060.txt new file mode 100644 index 0000000000..16a49fa3bc --- /dev/null +++ b/fastlane/metadata/android/sv-SE/changelogs/40104060.txt @@ -0,0 +1,2 @@ +Huvudsakliga ändringar i den här versionen: Trådtidslinje är nu live och snabbare. Diverse buggfixar och stabilitetsförbättringar. +Full ändringslogg: https://github.com/vector-im/element-android/releases/tag/v1.4.6 diff --git a/fastlane/metadata/android/sv-SE/changelogs/40104070.txt b/fastlane/metadata/android/sv-SE/changelogs/40104070.txt new file mode 100644 index 0000000000..01f79e85d6 --- /dev/null +++ b/fastlane/metadata/android/sv-SE/changelogs/40104070.txt @@ -0,0 +1,2 @@ +Huvudsakliga ändringar i den här versionen: Diverse buggfixar och stabilitetsförbättringar. +Full ändringslogg: https://github.com/vector-im/element-android/releases/tag/v1.4.7 diff --git a/fastlane/metadata/android/sv-SE/changelogs/40104080.txt b/fastlane/metadata/android/sv-SE/changelogs/40104080.txt new file mode 100644 index 0000000000..2ac7df2cda --- /dev/null +++ b/fastlane/metadata/android/sv-SE/changelogs/40104080.txt @@ -0,0 +1,2 @@ +Huvudsakliga ändringar i den här versionen: Trådtidslinje är nu live och snabbare. Diverse buggfixar och stabilitetsförbättringar. +Full ändringslogg: https://github.com/vector-im/element-android/releases/tag/v1.4.8 diff --git a/fastlane/metadata/android/sv-SE/changelogs/40104100.txt b/fastlane/metadata/android/sv-SE/changelogs/40104100.txt new file mode 100644 index 0000000000..a1585aacf5 --- /dev/null +++ b/fastlane/metadata/android/sv-SE/changelogs/40104100.txt @@ -0,0 +1,2 @@ +Huvudsakliga ändringar i denna version: Scrolla i röstmeddelanden. Diverse buggfixar och förbättrad stabilitet. +Fullständig lista över ändringar: https://github.com/vector-im/element-android/releases diff --git a/fastlane/metadata/android/sv-SE/changelogs/40104110.txt b/fastlane/metadata/android/sv-SE/changelogs/40104110.txt new file mode 100644 index 0000000000..92cce13669 --- /dev/null +++ b/fastlane/metadata/android/sv-SE/changelogs/40104110.txt @@ -0,0 +1,2 @@ +Huvudsakliga ändringar i denna version: Diverse buggfixar och förbättrad stabilitet. +Fullständig lista över ändringar: https://github.com/vector-im/element-android/releases diff --git a/fastlane/metadata/android/sv-SE/changelogs/40104120.txt b/fastlane/metadata/android/sv-SE/changelogs/40104120.txt new file mode 100644 index 0000000000..6692768e1e --- /dev/null +++ b/fastlane/metadata/android/sv-SE/changelogs/40104120.txt @@ -0,0 +1,2 @@ +Huvudsakliga ändringar i den här versionen: Låter användare visas offline och lägger till en ljudspelare för ljudbilagor +Full ändringslogg: https://github.com/vector-im/element-android/releases diff --git a/fastlane/metadata/android/sv-SE/changelogs/40104130.txt b/fastlane/metadata/android/sv-SE/changelogs/40104130.txt new file mode 100644 index 0000000000..6692768e1e --- /dev/null +++ b/fastlane/metadata/android/sv-SE/changelogs/40104130.txt @@ -0,0 +1,2 @@ +Huvudsakliga ändringar i den här versionen: Låter användare visas offline och lägger till en ljudspelare för ljudbilagor +Full ändringslogg: https://github.com/vector-im/element-android/releases diff --git a/fastlane/metadata/android/uk/changelogs/40104060.txt b/fastlane/metadata/android/uk/changelogs/40104060.txt new file mode 100644 index 0000000000..28f051724a --- /dev/null +++ b/fastlane/metadata/android/uk/changelogs/40104060.txt @@ -0,0 +1,2 @@ +Основні зміни в цій версії: Хронологія тредів працює наживо і швидше. Усунуто різні вади й поліпшено стабільність. +Вичерпний перелік змін: https://github.com/vector-im/element-android/releases/tag/v1.4.6 diff --git a/fastlane/metadata/android/uk/changelogs/40104070.txt b/fastlane/metadata/android/uk/changelogs/40104070.txt new file mode 100644 index 0000000000..dff28b7825 --- /dev/null +++ b/fastlane/metadata/android/uk/changelogs/40104070.txt @@ -0,0 +1,2 @@ +Основні зміни в цій версії: Усунуто різні вади й поліпшено стабільність. +Вичерпний перелік змін: https://github.com/vector-im/element-android/releases/tag/v1.4.7 diff --git a/fastlane/metadata/android/uk/changelogs/40104080.txt b/fastlane/metadata/android/uk/changelogs/40104080.txt new file mode 100644 index 0000000000..e5f7e7b271 --- /dev/null +++ b/fastlane/metadata/android/uk/changelogs/40104080.txt @@ -0,0 +1,2 @@ +Основні зміни в цій версії: Хронологія тредів працює наживо і швидше. Усунуто різні вади й поліпшено стабільність. +Вичерпний перелік змін: https://github.com/vector-im/element-android/releases diff --git a/fastlane/metadata/android/uk/changelogs/40104100.txt b/fastlane/metadata/android/uk/changelogs/40104100.txt new file mode 100644 index 0000000000..93c9fe4b70 --- /dev/null +++ b/fastlane/metadata/android/uk/changelogs/40104100.txt @@ -0,0 +1,2 @@ +Основні зміни в цій версії: Прокручування в голосових повідомленнях. Усунуто різні вади й поліпшено стабільність. +Вичерпний перелік змін: https://github.com/vector-im/element-android/releases diff --git a/fastlane/metadata/android/uk/changelogs/40104110.txt b/fastlane/metadata/android/uk/changelogs/40104110.txt new file mode 100644 index 0000000000..252a57c9d9 --- /dev/null +++ b/fastlane/metadata/android/uk/changelogs/40104110.txt @@ -0,0 +1,2 @@ +Основні зміни в цій версії: Усунуто різні вади й поліпшено стабільність. +Вичерпний перелік змін: https://github.com/vector-im/element-android/releases diff --git a/fastlane/metadata/android/uk/changelogs/40104120.txt b/fastlane/metadata/android/uk/changelogs/40104120.txt new file mode 100644 index 0000000000..aa075bd42e --- /dev/null +++ b/fastlane/metadata/android/uk/changelogs/40104120.txt @@ -0,0 +1,2 @@ +Основні зміни у цій версії: Дозволяє користувачам з’являтися в режимі офлайн та додає аудіопрогравач для аудіовкладень +Вичерпний перелік змін: https://github.com/vector-im/element-android/releases diff --git a/fastlane/metadata/android/uk/changelogs/40104130.txt b/fastlane/metadata/android/uk/changelogs/40104130.txt new file mode 100644 index 0000000000..aa075bd42e --- /dev/null +++ b/fastlane/metadata/android/uk/changelogs/40104130.txt @@ -0,0 +1,2 @@ +Основні зміни у цій версії: Дозволяє користувачам з’являтися в режимі офлайн та додає аудіопрогравач для аудіовкладень +Вичерпний перелік змін: https://github.com/vector-im/element-android/releases diff --git a/fastlane/metadata/android/zh-TW/changelogs/40104060.txt b/fastlane/metadata/android/zh-TW/changelogs/40104060.txt new file mode 100644 index 0000000000..316fad3363 --- /dev/null +++ b/fastlane/metadata/android/zh-TW/changelogs/40104060.txt @@ -0,0 +1,2 @@ +此版本中的主要變動:討論串時間軸現已更新,而且更快了。多個臭蟲修復與穩定性改善。 +完整的變更紀錄:https://github.com/vector-im/element-android/releases/tag/v1.4.6 diff --git a/fastlane/metadata/android/zh-TW/changelogs/40104070.txt b/fastlane/metadata/android/zh-TW/changelogs/40104070.txt new file mode 100644 index 0000000000..2cd9da666e --- /dev/null +++ b/fastlane/metadata/android/zh-TW/changelogs/40104070.txt @@ -0,0 +1,2 @@ +此版本中的主要變動:多個臭蟲修復與穩定性改善。 +完整的變更紀錄:https://github.com/vector-im/element-android/releases/tag/v1.4.7 diff --git a/fastlane/metadata/android/zh-TW/changelogs/40104080.txt b/fastlane/metadata/android/zh-TW/changelogs/40104080.txt new file mode 100644 index 0000000000..c036aa7d56 --- /dev/null +++ b/fastlane/metadata/android/zh-TW/changelogs/40104080.txt @@ -0,0 +1,2 @@ +此版本中的主要變動:討論串時間軸現已更新,而且更快了。多個臭蟲修復與穩定性改善。 +完整的變更紀錄:https://github.com/vector-im/element-android/releases diff --git a/fastlane/metadata/android/zh-TW/changelogs/40104100.txt b/fastlane/metadata/android/zh-TW/changelogs/40104100.txt new file mode 100644 index 0000000000..c78ed7dd2d --- /dev/null +++ b/fastlane/metadata/android/zh-TW/changelogs/40104100.txt @@ -0,0 +1,2 @@ +此版本中的主要變動:捲動音訊訊息。多個臭蟲修復與穩定性改善。 +完整的變更紀錄:https://github.com/vector-im/element-android/releases diff --git a/fastlane/metadata/android/zh-TW/changelogs/40104110.txt b/fastlane/metadata/android/zh-TW/changelogs/40104110.txt new file mode 100644 index 0000000000..4bcca9a0b8 --- /dev/null +++ b/fastlane/metadata/android/zh-TW/changelogs/40104110.txt @@ -0,0 +1,2 @@ +此版本中的主要變動:多個臭蟲修復與穩定性改善。 +完整的變更紀錄:https://github.com/vector-im/element-android/releases diff --git a/fastlane/metadata/android/zh-TW/changelogs/40104120.txt b/fastlane/metadata/android/zh-TW/changelogs/40104120.txt new file mode 100644 index 0000000000..d3d48abab9 --- /dev/null +++ b/fastlane/metadata/android/zh-TW/changelogs/40104120.txt @@ -0,0 +1,2 @@ +此版本中的主要變動:允許使用者顯示為離線並為音訊附件新增音訊播放器 +完整的變更紀錄:https://github.com/vector-im/element-android/releases diff --git a/fastlane/metadata/android/zh-TW/changelogs/40104130.txt b/fastlane/metadata/android/zh-TW/changelogs/40104130.txt new file mode 100644 index 0000000000..d3d48abab9 --- /dev/null +++ b/fastlane/metadata/android/zh-TW/changelogs/40104130.txt @@ -0,0 +1,2 @@ +此版本中的主要變動:允許使用者顯示為離線並為音訊附件新增音訊播放器 +完整的變更紀錄:https://github.com/vector-im/element-android/releases diff --git a/library/attachment-viewer/src/main/java/im/vector/lib/attachmentviewer/ImageLoaderTarget.kt b/library/attachment-viewer/src/main/java/im/vector/lib/attachmentviewer/ImageLoaderTarget.kt index 99686eaabb..7b54438a52 100644 --- a/library/attachment-viewer/src/main/java/im/vector/lib/attachmentviewer/ImageLoaderTarget.kt +++ b/library/attachment-viewer/src/main/java/im/vector/lib/attachmentviewer/ImageLoaderTarget.kt @@ -37,7 +37,7 @@ interface ImageLoaderTarget { } internal class DefaultImageLoaderTarget(val holder: AnimatedImageViewHolder, private val contextView: ImageView) : - ImageLoaderTarget { + ImageLoaderTarget { override fun contextView(): ImageView { return contextView } diff --git a/library/core-utils/build.gradle b/library/core-utils/build.gradle index 1cc518c432..0f7789a2a8 100644 --- a/library/core-utils/build.gradle +++ b/library/core-utils/build.gradle @@ -44,7 +44,7 @@ android { kotlinOptions { jvmTarget = "11" freeCompilerArgs += [ - "-Xopt-in=kotlin.RequiresOptIn" + "-opt-in=kotlin.RequiresOptIn" ] } } diff --git a/library/core-utils/src/main/java/im/vector/lib/core/utils/epoxy/charsequence/EpoxyCharSequence.kt b/library/core-utils/src/main/java/im/vector/lib/core/utils/epoxy/charsequence/EpoxyCharSequence.kt index 77e2d58001..f0f01bc6ea 100644 --- a/library/core-utils/src/main/java/im/vector/lib/core/utils/epoxy/charsequence/EpoxyCharSequence.kt +++ b/library/core-utils/src/main/java/im/vector/lib/core/utils/epoxy/charsequence/EpoxyCharSequence.kt @@ -17,7 +17,7 @@ package im.vector.lib.core.utils.epoxy.charsequence /** - * Wrapper for a CharSequence, which support mutation of the CharSequence, which can happen during rendering + * Wrapper for a CharSequence, which support mutation of the CharSequence, which can happen during rendering. */ class EpoxyCharSequence(val charSequence: CharSequence) { private val hash = charSequence.toString().hashCode() diff --git a/library/core-utils/src/main/java/im/vector/lib/core/utils/epoxy/charsequence/Extensions.kt b/library/core-utils/src/main/java/im/vector/lib/core/utils/epoxy/charsequence/Extensions.kt index ba0f0b9ad6..c52eaedbb7 100644 --- a/library/core-utils/src/main/java/im/vector/lib/core/utils/epoxy/charsequence/Extensions.kt +++ b/library/core-utils/src/main/java/im/vector/lib/core/utils/epoxy/charsequence/Extensions.kt @@ -17,6 +17,6 @@ package im.vector.lib.core.utils.epoxy.charsequence /** - * Extensions to wrap CharSequence to EpoxyCharSequence + * Extensions to wrap CharSequence to EpoxyCharSequence. */ fun CharSequence.toEpoxyCharSequence() = EpoxyCharSequence(this) diff --git a/library/jsonviewer/build.gradle b/library/jsonviewer/build.gradle index 0cad8ac171..2110747feb 100644 --- a/library/jsonviewer/build.gradle +++ b/library/jsonviewer/build.gradle @@ -6,8 +6,14 @@ apply plugin: 'com.jakewharton.butterknife' buildscript { repositories { - google() - mavenCentral() + // Do not use `google()`, it prevents Dependabot from working properly + maven { + url 'https://maven.google.com' + } + // Do not use `mavenCentral()`, it prevents Dependabot from working properly + maven { + url 'https://repo1.maven.org/maven2' + } } dependencies { classpath 'com.jakewharton:butterknife-gradle-plugin:10.2.3' diff --git a/library/jsonviewer/src/main/java/org/billcarsonfr/jsonviewer/JSonViewerDialog.kt b/library/jsonviewer/src/main/java/org/billcarsonfr/jsonviewer/JSonViewerDialog.kt index a8d9cac849..0ebf539d4d 100644 --- a/library/jsonviewer/src/main/java/org/billcarsonfr/jsonviewer/JSonViewerDialog.kt +++ b/library/jsonviewer/src/main/java/org/billcarsonfr/jsonviewer/JSonViewerDialog.kt @@ -27,9 +27,9 @@ import com.airbnb.mvrx.Mavericks class JSonViewerDialog : DialogFragment() { override fun onCreateView( - inflater: LayoutInflater, - container: ViewGroup?, - savedInstanceState: Bundle? + inflater: LayoutInflater, + container: ViewGroup?, + savedInstanceState: Bundle? ): View? { return inflater.inflate(R.layout.fragment_dialog_jv, container, false) } @@ -39,15 +39,15 @@ class JSonViewerDialog : DialogFragment() { val args: JSonViewerFragmentArgs = arguments?.getParcelable(Mavericks.KEY_ARG) ?: return if (savedInstanceState == null) { childFragmentManager.beginTransaction() - .replace( - R.id.fragmentContainer, JSonViewerFragment.newInstance( - args.jsonString, - args.defaultOpenDepth, - true, - args.styleProvider + .replace( + R.id.fragmentContainer, JSonViewerFragment.newInstance( + args.jsonString, + args.defaultOpenDepth, + true, + args.styleProvider ) - ) - .commitNow() + ) + .commitNow() } } @@ -63,13 +63,13 @@ class JSonViewerDialog : DialogFragment() { companion object { fun newInstance( - jsonString: String, - initialOpenDepth: Int = -1, - styleProvider: JSonViewerStyleProvider? = null + jsonString: String, + initialOpenDepth: Int = -1, + styleProvider: JSonViewerStyleProvider? = null ): JSonViewerDialog { val args = Bundle() val parcelableArgs = - JSonViewerFragmentArgs(jsonString, initialOpenDepth, false, styleProvider) + JSonViewerFragmentArgs(jsonString, initialOpenDepth, false, styleProvider) args.putParcelable(Mavericks.KEY_ARG, parcelableArgs) return JSonViewerDialog().apply { arguments = args } } diff --git a/library/jsonviewer/src/main/java/org/billcarsonfr/jsonviewer/JSonViewerFragment.kt b/library/jsonviewer/src/main/java/org/billcarsonfr/jsonviewer/JSonViewerFragment.kt index 51e2797958..fbf6f88bc3 100644 --- a/library/jsonviewer/src/main/java/org/billcarsonfr/jsonviewer/JSonViewerFragment.kt +++ b/library/jsonviewer/src/main/java/org/billcarsonfr/jsonviewer/JSonViewerFragment.kt @@ -32,10 +32,10 @@ import kotlinx.parcelize.Parcelize @Parcelize internal data class JSonViewerFragmentArgs( - val jsonString: String, - val defaultOpenDepth: Int, - val wrap: Boolean, - val styleProvider: JSonViewerStyleProvider? + val jsonString: String, + val defaultOpenDepth: Int, + val wrap: Boolean, + val styleProvider: JSonViewerStyleProvider? ) : Parcelable class JSonViewerFragment : Fragment(), MavericksView { @@ -49,20 +49,20 @@ class JSonViewerFragment : Fragment(), MavericksView { private lateinit var recyclerView: EpoxyRecyclerView override fun onCreateView( - inflater: LayoutInflater, - container: ViewGroup?, - savedInstanceState: Bundle? + inflater: LayoutInflater, + container: ViewGroup?, + savedInstanceState: Bundle? ): View? { val args: JSonViewerFragmentArgs? = arguments?.getParcelable(Mavericks.KEY_ARG) val inflate = - if (args?.wrap == true) { - inflater.inflate(R.layout.fragment_jv_recycler_view_wrap, container, false) - } else { - inflater.inflate(R.layout.fragment_jv_recycler_view, container, false) - } + if (args?.wrap == true) { + inflater.inflate(R.layout.fragment_jv_recycler_view_wrap, container, false) + } else { + inflater.inflate(R.layout.fragment_jv_recycler_view, container, false) + } recyclerView = inflate.findViewById(R.id.jvRecyclerView) recyclerView.layoutManager = - LinearLayoutManager(context, LinearLayoutManager.VERTICAL, false) + LinearLayoutManager(context, LinearLayoutManager.VERTICAL, false) recyclerView.setController(epoxyController) epoxyController.setStyle(args?.styleProvider) registerForContextMenu(recyclerView) @@ -79,21 +79,21 @@ class JSonViewerFragment : Fragment(), MavericksView { companion object { fun newInstance( - jsonString: String, - initialOpenDepth: Int = -1, - wrap: Boolean = false, - styleProvider: JSonViewerStyleProvider? = null + jsonString: String, + initialOpenDepth: Int = -1, + wrap: Boolean = false, + styleProvider: JSonViewerStyleProvider? = null ): JSonViewerFragment { return JSonViewerFragment().apply { arguments = Bundle().apply { putParcelable( - Mavericks.KEY_ARG, - JSonViewerFragmentArgs( - jsonString, - initialOpenDepth, - wrap, - styleProvider - ) + Mavericks.KEY_ARG, + JSonViewerFragmentArgs( + jsonString, + initialOpenDepth, + wrap, + styleProvider + ) ) } } diff --git a/library/jsonviewer/src/main/java/org/billcarsonfr/jsonviewer/JSonViewerModel.kt b/library/jsonviewer/src/main/java/org/billcarsonfr/jsonviewer/JSonViewerModel.kt index 3d1f8dd3e2..6940e79e3f 100644 --- a/library/jsonviewer/src/main/java/org/billcarsonfr/jsonviewer/JSonViewerModel.kt +++ b/library/jsonviewer/src/main/java/org/billcarsonfr/jsonviewer/JSonViewerModel.kt @@ -30,8 +30,8 @@ internal interface Composed { } internal class JSonViewerObject(key: String?, index: Int?, jObject: JSONObject) : - JSonViewerModel(key, index, jObject), - Composed { + JSonViewerModel(key, index, jObject), + Composed { var keys = LinkedHashMap() @@ -41,7 +41,7 @@ internal class JSonViewerObject(key: String?, index: Int?, jObject: JSONObject) } internal class JSonViewerArray(key: String?, index: Int?, jObject: JSONArray) : - JSonViewerModel(key, index, jObject), Composed { + JSonViewerModel(key, index, jObject), Composed { var items = ArrayList() override fun addChild(model: JSonViewerModel) { @@ -50,7 +50,7 @@ internal class JSonViewerArray(key: String?, index: Int?, jObject: JSONArray) : } internal class JSonViewerLeaf(key: String?, index: Int?, val stringRes: String, val type: JSONType) : - JSonViewerModel(key, index, stringRes) + JSonViewerModel(key, index, stringRes) internal enum class JSONType { STRING, @@ -75,41 +75,41 @@ internal object ModelParser { when (obj) { is JSONObject -> { val objectComposed = JSonViewerObject(key, index, obj) - .apply { isExpanded = initialOpenDepth == -1 || depth <= initialOpenDepth } + .apply { isExpanded = initialOpenDepth == -1 || depth <= initialOpenDepth } objectComposed.depth = depth obj.keys().forEach { eval(objectComposed, it, null, obj.get(it), depth + 1, initialOpenDepth) } parent.addChild(objectComposed) } - is JSONArray -> { + is JSONArray -> { val objectComposed = JSonViewerArray(key, index, obj) - .apply { isExpanded = initialOpenDepth == -1 || depth <= initialOpenDepth } + .apply { isExpanded = initialOpenDepth == -1 || depth <= initialOpenDepth } objectComposed.depth = depth for (i in 0 until obj.length()) { eval(objectComposed, null, i, obj[i], depth + 1, initialOpenDepth) } parent.addChild(objectComposed) } - is String -> { - JSonViewerLeaf(key, index, obj, JSONType.STRING).let { + is String -> { + JSonViewerLeaf(key, index, obj, JSONType.STRING).let { it.depth = depth parent.addChild(it) } } - is Number -> { + is Number -> { JSonViewerLeaf(key, index, obj.toString(), JSONType.NUMBER).let { it.depth = depth parent.addChild(it) } } - is Boolean -> { + is Boolean -> { JSonViewerLeaf(key, index, obj.toString(), JSONType.BOOLEAN).let { it.depth = depth parent.addChild(it) } } - else -> { + else -> { if (obj == JSONObject.NULL) { JSonViewerLeaf(key, index, "null", JSONType.NULL).let { it.depth = depth diff --git a/library/jsonviewer/src/main/java/org/billcarsonfr/jsonviewer/JSonViewerStyleProvider.kt b/library/jsonviewer/src/main/java/org/billcarsonfr/jsonviewer/JSonViewerStyleProvider.kt index 4fc04c91e4..17d8034f2d 100644 --- a/library/jsonviewer/src/main/java/org/billcarsonfr/jsonviewer/JSonViewerStyleProvider.kt +++ b/library/jsonviewer/src/main/java/org/billcarsonfr/jsonviewer/JSonViewerStyleProvider.kt @@ -24,22 +24,22 @@ import kotlinx.parcelize.Parcelize @Parcelize data class JSonViewerStyleProvider( - @ColorInt val keyColor: Int, - @ColorInt val stringColor: Int, - @ColorInt val booleanColor: Int, - @ColorInt val numberColor: Int, - @ColorInt val baseColor: Int, - @ColorInt val secondaryColor: Int + @ColorInt val keyColor: Int, + @ColorInt val stringColor: Int, + @ColorInt val booleanColor: Int, + @ColorInt val numberColor: Int, + @ColorInt val baseColor: Int, + @ColorInt val secondaryColor: Int ) : Parcelable { companion object { fun default(context: Context) = JSonViewerStyleProvider( - keyColor = ContextCompat.getColor(context, R.color.key_color), - stringColor = ContextCompat.getColor(context, R.color.string_color), - booleanColor = ContextCompat.getColor(context, R.color.bool_color), - numberColor = ContextCompat.getColor(context, R.color.number_color), - baseColor = ContextCompat.getColor(context, R.color.base_color), - secondaryColor = ContextCompat.getColor(context, R.color.secondary_color) + keyColor = ContextCompat.getColor(context, R.color.key_color), + stringColor = ContextCompat.getColor(context, R.color.string_color), + booleanColor = ContextCompat.getColor(context, R.color.bool_color), + numberColor = ContextCompat.getColor(context, R.color.number_color), + baseColor = ContextCompat.getColor(context, R.color.base_color), + secondaryColor = ContextCompat.getColor(context, R.color.secondary_color) ) } } diff --git a/library/jsonviewer/src/main/java/org/billcarsonfr/jsonviewer/JSonViewerViewModel.kt b/library/jsonviewer/src/main/java/org/billcarsonfr/jsonviewer/JSonViewerViewModel.kt index bc3f022cfa..d4e8f42604 100644 --- a/library/jsonviewer/src/main/java/org/billcarsonfr/jsonviewer/JSonViewerViewModel.kt +++ b/library/jsonviewer/src/main/java/org/billcarsonfr/jsonviewer/JSonViewerViewModel.kt @@ -28,11 +28,11 @@ import com.airbnb.mvrx.ViewModelContext import kotlinx.coroutines.launch internal data class JSonViewerState( - val root: Async = Uninitialized + val root: Async = Uninitialized ) : MavericksState internal class JSonViewerViewModel(initialState: JSonViewerState) : - MavericksViewModel(initialState) { + MavericksViewModel(initialState) { fun setJsonSource(json: String, initialOpenDepth: Int) { setState { @@ -43,14 +43,14 @@ internal class JSonViewerViewModel(initialState: JSonViewerState) : ModelParser.fromJsonString(json, initialOpenDepth).let { setState { copy( - root = Success(it) + root = Success(it) ) } } } catch (error: Throwable) { setState { copy( - root = Fail(error) + root = Fail(error) ) } } @@ -64,7 +64,7 @@ internal class JSonViewerViewModel(initialState: JSonViewerState) : val arg: JSonViewerFragmentArgs = viewModelContext.args() return try { JSonViewerState( - Success(ModelParser.fromJsonString(arg.jsonString, arg.defaultOpenDepth)) + Success(ModelParser.fromJsonString(arg.jsonString, arg.defaultOpenDepth)) ) } catch (failure: Throwable) { JSonViewerState(Fail(failure)) diff --git a/library/jsonviewer/src/main/java/org/billcarsonfr/jsonviewer/Utils.kt b/library/jsonviewer/src/main/java/org/billcarsonfr/jsonviewer/Utils.kt index efb2bfd855..0ac1cfe5f6 100644 --- a/library/jsonviewer/src/main/java/org/billcarsonfr/jsonviewer/Utils.kt +++ b/library/jsonviewer/src/main/java/org/billcarsonfr/jsonviewer/Utils.kt @@ -22,9 +22,9 @@ import android.util.TypedValue internal object Utils { fun dpToPx(dp: Int, context: Context): Int { return TypedValue.applyDimension( - TypedValue.COMPLEX_UNIT_DIP, - dp.toFloat(), - context.resources.displayMetrics + TypedValue.COMPLEX_UNIT_DIP, + dp.toFloat(), + context.resources.displayMetrics ).toInt() } } diff --git a/library/jsonviewer/src/main/java/org/billcarsonfr/jsonviewer/ValueItem.kt b/library/jsonviewer/src/main/java/org/billcarsonfr/jsonviewer/ValueItem.kt index 00d66645e6..fac7099b37 100644 --- a/library/jsonviewer/src/main/java/org/billcarsonfr/jsonviewer/ValueItem.kt +++ b/library/jsonviewer/src/main/java/org/billcarsonfr/jsonviewer/ValueItem.kt @@ -71,14 +71,14 @@ internal abstract class ValueItem : EpoxyModelWithHolder() { } override fun onCreateContextMenu( - menu: ContextMenu?, - v: View?, - menuInfo: ContextMenu.ContextMenuInfo? + menu: ContextMenu?, + v: View?, + menuInfo: ContextMenu.ContextMenuInfo? ) { if (copyValue != null) { val menuItem = menu?.add(R.string.copy_value) val clipService = - v?.context?.getSystemService(Context.CLIPBOARD_SERVICE) as? ClipboardManager + v?.context?.getSystemService(Context.CLIPBOARD_SERVICE) as? ClipboardManager menuItem?.setOnMenuItemClickListener { clipService?.setPrimaryClip(ClipData.newPlainText("", copyValue)) true diff --git a/library/multipicker/src/main/java/im/vector/lib/multipicker/AudioPicker.kt b/library/multipicker/src/main/java/im/vector/lib/multipicker/AudioPicker.kt index 739bda7004..3d6fdb96fc 100644 --- a/library/multipicker/src/main/java/im/vector/lib/multipicker/AudioPicker.kt +++ b/library/multipicker/src/main/java/im/vector/lib/multipicker/AudioPicker.kt @@ -22,7 +22,7 @@ import im.vector.lib.multipicker.entity.MultiPickerAudioType import im.vector.lib.multipicker.utils.toMultiPickerAudioType /** - * Audio file picker implementation + * Audio file picker implementation. */ class AudioPicker : Picker() { diff --git a/library/multipicker/src/main/java/im/vector/lib/multipicker/CameraPicker.kt b/library/multipicker/src/main/java/im/vector/lib/multipicker/CameraPicker.kt index b1442a56e1..c4a2ebbea9 100644 --- a/library/multipicker/src/main/java/im/vector/lib/multipicker/CameraPicker.kt +++ b/library/multipicker/src/main/java/im/vector/lib/multipicker/CameraPicker.kt @@ -23,22 +23,20 @@ import android.provider.MediaStore import androidx.activity.result.ActivityResultLauncher import androidx.core.content.FileProvider import im.vector.lib.multipicker.entity.MultiPickerImageType +import im.vector.lib.multipicker.utils.MediaType +import im.vector.lib.multipicker.utils.createTemporaryMediaFile import im.vector.lib.multipicker.utils.toMultiPickerImageType -import java.io.File -import java.text.SimpleDateFormat -import java.util.Date -import java.util.Locale /** - * Implementation of taking a photo with Camera + * Implementation of taking a photo with Camera. */ class CameraPicker { /** - * Start camera by using a ActivityResultLauncher + * Start camera by using a ActivityResultLauncher. * @return Uri of taken photo or null if the operation is cancelled. */ - fun startWithExpectingFile(context: Context, activityResultLauncher: ActivityResultLauncher): Uri? { + fun startWithExpectingFile(context: Context, activityResultLauncher: ActivityResultLauncher): Uri { val photoUri = createPhotoUri(context) val intent = createIntent().apply { putExtra(MediaStore.EXTRA_OUTPUT, photoUri) @@ -63,19 +61,9 @@ class CameraPicker { companion object { fun createPhotoUri(context: Context): Uri { - val file = createImageFile(context) + val file = createTemporaryMediaFile(context, MediaType.IMAGE) val authority = context.packageName + ".multipicker.fileprovider" return FileProvider.getUriForFile(context, authority, file) } - - private fun createImageFile(context: Context): File { - val timeStamp: String = SimpleDateFormat("yyyyMMdd_HHmmss", Locale.getDefault()).format(Date()) - val storageDir: File = context.filesDir - return File.createTempFile( - "${timeStamp}_", /* prefix */ - ".jpg", /* suffix */ - storageDir /* directory */ - ) - } } } diff --git a/library/multipicker/src/main/java/im/vector/lib/multipicker/CameraVideoPicker.kt b/library/multipicker/src/main/java/im/vector/lib/multipicker/CameraVideoPicker.kt index 76342b6e2e..e042e2366a 100644 --- a/library/multipicker/src/main/java/im/vector/lib/multipicker/CameraVideoPicker.kt +++ b/library/multipicker/src/main/java/im/vector/lib/multipicker/CameraVideoPicker.kt @@ -23,22 +23,20 @@ import android.provider.MediaStore import androidx.activity.result.ActivityResultLauncher import androidx.core.content.FileProvider import im.vector.lib.multipicker.entity.MultiPickerVideoType +import im.vector.lib.multipicker.utils.MediaType +import im.vector.lib.multipicker.utils.createTemporaryMediaFile import im.vector.lib.multipicker.utils.toMultiPickerVideoType -import java.io.File -import java.text.SimpleDateFormat -import java.util.Date -import java.util.Locale /** - * Implementation of taking a video with Camera + * Implementation of taking a video with Camera. */ class CameraVideoPicker { /** - * Start camera by using a ActivityResultLauncher + * Start camera by using a ActivityResultLauncher. * @return Uri of taken photo or null if the operation is cancelled. */ - fun startWithExpectingFile(context: Context, activityResultLauncher: ActivityResultLauncher): Uri? { + fun startWithExpectingFile(context: Context, activityResultLauncher: ActivityResultLauncher): Uri { val videoUri = createVideoUri(context) val intent = createIntent().apply { putExtra(MediaStore.EXTRA_OUTPUT, videoUri) @@ -63,19 +61,9 @@ class CameraVideoPicker { companion object { fun createVideoUri(context: Context): Uri { - val file = createVideoFile(context) + val file = createTemporaryMediaFile(context, MediaType.VIDEO) val authority = context.packageName + ".multipicker.fileprovider" return FileProvider.getUriForFile(context, authority, file) } - - private fun createVideoFile(context: Context): File { - val timeStamp: String = SimpleDateFormat("yyyyMMdd_HHmmss", Locale.getDefault()).format(Date()) - val storageDir: File = context.filesDir - return File.createTempFile( - "${timeStamp}_", /* prefix */ - ".mp4", /* suffix */ - storageDir /* directory */ - ) - } } } diff --git a/library/multipicker/src/main/java/im/vector/lib/multipicker/ContactPicker.kt b/library/multipicker/src/main/java/im/vector/lib/multipicker/ContactPicker.kt index bb21196858..be83d2ea47 100644 --- a/library/multipicker/src/main/java/im/vector/lib/multipicker/ContactPicker.kt +++ b/library/multipicker/src/main/java/im/vector/lib/multipicker/ContactPicker.kt @@ -26,7 +26,7 @@ import im.vector.lib.multipicker.entity.MultiPickerContactType import im.vector.lib.multipicker.utils.getColumnIndexOrNull /** - * Contact Picker implementation + * Contact Picker implementation. */ class ContactPicker : Picker() { diff --git a/library/multipicker/src/main/java/im/vector/lib/multipicker/FilePicker.kt b/library/multipicker/src/main/java/im/vector/lib/multipicker/FilePicker.kt index 2e3148c9de..13ef5aa637 100644 --- a/library/multipicker/src/main/java/im/vector/lib/multipicker/FilePicker.kt +++ b/library/multipicker/src/main/java/im/vector/lib/multipicker/FilePicker.kt @@ -32,7 +32,7 @@ import im.vector.lib.multipicker.utils.toMultiPickerImageType import im.vector.lib.multipicker.utils.toMultiPickerVideoType /** - * Implementation of selecting any type of files + * Implementation of selecting any type of files. */ class FilePicker : Picker() { diff --git a/library/multipicker/src/main/java/im/vector/lib/multipicker/ImagePicker.kt b/library/multipicker/src/main/java/im/vector/lib/multipicker/ImagePicker.kt index 4cc2352109..bc5a13558a 100644 --- a/library/multipicker/src/main/java/im/vector/lib/multipicker/ImagePicker.kt +++ b/library/multipicker/src/main/java/im/vector/lib/multipicker/ImagePicker.kt @@ -22,7 +22,7 @@ import im.vector.lib.multipicker.entity.MultiPickerImageType import im.vector.lib.multipicker.utils.toMultiPickerImageType /** - * Image Picker implementation + * Image Picker implementation. */ class ImagePicker : Picker() { diff --git a/library/multipicker/src/main/java/im/vector/lib/multipicker/MediaPicker.kt b/library/multipicker/src/main/java/im/vector/lib/multipicker/MediaPicker.kt index c58abde694..db74dbf9ff 100644 --- a/library/multipicker/src/main/java/im/vector/lib/multipicker/MediaPicker.kt +++ b/library/multipicker/src/main/java/im/vector/lib/multipicker/MediaPicker.kt @@ -24,7 +24,7 @@ import im.vector.lib.multipicker.utils.toMultiPickerImageType import im.vector.lib.multipicker.utils.toMultiPickerVideoType /** - * Image/Video Picker implementation + * Image/Video Picker implementation. */ class MediaPicker : Picker() { diff --git a/library/multipicker/src/main/java/im/vector/lib/multipicker/MultiPicker.kt b/library/multipicker/src/main/java/im/vector/lib/multipicker/MultiPicker.kt index 6ce50f622a..e7883c9e53 100644 --- a/library/multipicker/src/main/java/im/vector/lib/multipicker/MultiPicker.kt +++ b/library/multipicker/src/main/java/im/vector/lib/multipicker/MultiPicker.kt @@ -16,7 +16,7 @@ package im.vector.lib.multipicker -class MultiPicker { +class MultiPicker private constructor() { companion object Type { val IMAGE by lazy { MultiPicker() } @@ -31,15 +31,15 @@ class MultiPicker { @Suppress("UNCHECKED_CAST") fun get(type: MultiPicker): T { return when (type) { - IMAGE -> ImagePicker() as T - VIDEO -> VideoPicker() as T - MEDIA -> MediaPicker() as T - FILE -> FilePicker() as T - AUDIO -> AudioPicker() as T - CONTACT -> ContactPicker() as T - CAMERA -> CameraPicker() as T - CAMERA_VIDEO -> CameraVideoPicker() as T - else -> throw IllegalArgumentException("Unsupported type $type") + IMAGE -> ImagePicker() as T + VIDEO -> VideoPicker() as T + MEDIA -> MediaPicker() as T + FILE -> FilePicker() as T + AUDIO -> AudioPicker() as T + CONTACT -> ContactPicker() as T + CAMERA -> CameraPicker() as T + CAMERA_VIDEO -> CameraVideoPicker() as T + else -> throw IllegalArgumentException("Unsupported type $type") } } } diff --git a/library/multipicker/src/main/java/im/vector/lib/multipicker/Picker.kt b/library/multipicker/src/main/java/im/vector/lib/multipicker/Picker.kt index ba765a3b1d..8960f3228b 100644 --- a/library/multipicker/src/main/java/im/vector/lib/multipicker/Picker.kt +++ b/library/multipicker/src/main/java/im/vector/lib/multipicker/Picker.kt @@ -24,7 +24,7 @@ import android.net.Uri import androidx.activity.result.ActivityResultLauncher /** - * Abstract class to provide all types of Pickers + * Abstract class to provide all types of Pickers. */ abstract class Picker { diff --git a/library/multipicker/src/main/java/im/vector/lib/multipicker/VideoPicker.kt b/library/multipicker/src/main/java/im/vector/lib/multipicker/VideoPicker.kt index 6b6bc52c1b..89bb1af6aa 100644 --- a/library/multipicker/src/main/java/im/vector/lib/multipicker/VideoPicker.kt +++ b/library/multipicker/src/main/java/im/vector/lib/multipicker/VideoPicker.kt @@ -22,7 +22,7 @@ import im.vector.lib.multipicker.entity.MultiPickerVideoType import im.vector.lib.multipicker.utils.toMultiPickerVideoType /** - * Video Picker implementation + * Video Picker implementation. */ class VideoPicker : Picker() { diff --git a/library/multipicker/src/main/java/im/vector/lib/multipicker/utils/MediaFileUtils.kt b/library/multipicker/src/main/java/im/vector/lib/multipicker/utils/MediaFileUtils.kt new file mode 100644 index 0000000000..a029d5e6b1 --- /dev/null +++ b/library/multipicker/src/main/java/im/vector/lib/multipicker/utils/MediaFileUtils.kt @@ -0,0 +1,42 @@ +/* + * 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.lib.multipicker.utils + +import android.content.Context +import java.io.File +import java.text.SimpleDateFormat +import java.util.Date +import java.util.Locale + +internal fun createTemporaryMediaFile(context: Context, mediaType: MediaType): File { + val timeStamp: String = SimpleDateFormat("yyyyMMdd_HHmmss", Locale.getDefault()).format(Date()) + val storageDir: File = context.filesDir.also { it.mkdirs() } + val fileSuffix = when (mediaType) { + MediaType.IMAGE -> ".jpg" + MediaType.VIDEO -> ".mp4" + } + + return File.createTempFile( + "${timeStamp}_", + fileSuffix, + storageDir + ) +} + +internal enum class MediaType { + IMAGE, VIDEO +} diff --git a/library/ui-styles/src/main/res/values/colors.xml b/library/ui-styles/src/main/res/values/colors.xml index d887e7774e..e72d02f51e 100644 --- a/library/ui-styles/src/main/res/values/colors.xml +++ b/library/ui-styles/src/main/res/values/colors.xml @@ -126,6 +126,14 @@ @color/palette_element_green @color/palette_element_green + + @color/element_alert_light + @color/element_alert_dark + + + @color/palette_element_orange + @color/palette_element_orange + @color/palette_prune @@ -136,4 +144,9 @@ #17191C #FF4B55 + + + @color/palette_white + @color/palette_black_950 + diff --git a/library/ui-styles/src/main/res/values/dimens.xml b/library/ui-styles/src/main/res/values/dimens.xml index 81d5a77297..70d051b457 100644 --- a/library/ui-styles/src/main/res/values/dimens.xml +++ b/library/ui-styles/src/main/res/values/dimens.xml @@ -9,6 +9,7 @@ 32dp 50dp + 20dp 16dp 32dp @@ -40,7 +41,7 @@ 24dp 48dp 48dp - 38dp + 34dp 56dp diff --git a/library/ui-styles/src/main/res/values/palette.xml b/library/ui-styles/src/main/res/values/palette.xml index e6cee80b59..73ac768919 100644 --- a/library/ui-styles/src/main/res/values/palette.xml +++ b/library/ui-styles/src/main/res/values/palette.xml @@ -17,6 +17,7 @@ #FF812D #0DBD8B + #D9B072 #FFFFFF #FF5B55 diff --git a/library/ui-styles/src/main/res/values/theme_dark.xml b/library/ui-styles/src/main/res/values/theme_dark.xml index 7177687fdd..733f7e8eb5 100644 --- a/library/ui-styles/src/main/res/values/theme_dark.xml +++ b/library/ui-styles/src/main/res/values/theme_dark.xml @@ -30,6 +30,7 @@ @color/element_system_dark @color/vctr_message_bubble_inbound_dark @color/vctr_message_bubble_outbound_dark + @color/vctr_badge_color_border_dark #61708B @@ -44,6 +45,8 @@ @color/vctr_presence_indicator_offline_dark @color/vctr_presence_indicator_online_dark + @color/vctr_presence_indicator_busy_dark + @color/vctr_presence_indicator_away_dark ?vctr_system diff --git a/library/ui-styles/src/main/res/values/theme_light.xml b/library/ui-styles/src/main/res/values/theme_light.xml index c90c021591..77996c8ce5 100644 --- a/library/ui-styles/src/main/res/values/theme_light.xml +++ b/library/ui-styles/src/main/res/values/theme_light.xml @@ -30,6 +30,7 @@ @color/element_background_light @color/vctr_message_bubble_inbound_light @color/vctr_message_bubble_outbound_light + @color/vctr_badge_color_border_light #61708B @@ -44,6 +45,8 @@ @color/vctr_presence_indicator_offline_light @color/vctr_presence_indicator_online_light + @color/vctr_presence_indicator_busy_light + @color/vctr_presence_indicator_away_light ?vctr_system diff --git a/matrix-sdk-android-flow/src/main/java/org/matrix/android/sdk/flow/FlowRoom.kt b/matrix-sdk-android-flow/src/main/java/org/matrix/android/sdk/flow/FlowRoom.kt index d737715306..90546756b8 100644 --- a/matrix-sdk-android-flow/src/main/java/org/matrix/android/sdk/flow/FlowRoom.kt +++ b/matrix-sdk-android-flow/src/main/java/org/matrix/android/sdk/flow/FlowRoom.kt @@ -21,6 +21,8 @@ import kotlinx.coroutines.flow.Flow import org.matrix.android.sdk.api.query.QueryStringValue import org.matrix.android.sdk.api.session.events.model.Event import org.matrix.android.sdk.api.session.room.Room +import org.matrix.android.sdk.api.session.room.getStateEvent +import org.matrix.android.sdk.api.session.room.getTimelineEvent import org.matrix.android.sdk.api.session.room.members.RoomMemberQueryParams import org.matrix.android.sdk.api.session.room.model.EventAnnotationsSummary import org.matrix.android.sdk.api.session.room.model.ReadReceipt @@ -45,79 +47,81 @@ class FlowRoom(private val room: Room) { } fun liveRoomMembers(queryParams: RoomMemberQueryParams): Flow> { - return room.getRoomMembersLive(queryParams).asFlow() + return room.membershipService().getRoomMembersLive(queryParams).asFlow() .startWith(room.coroutineDispatchers.io) { - room.getRoomMembers(queryParams) + room.membershipService().getRoomMembers(queryParams) } } fun liveAnnotationSummary(eventId: String): Flow> { - return room.getEventAnnotationsSummaryLive(eventId).asFlow() + return room.relationService().getEventAnnotationsSummaryLive(eventId).asFlow() .startWith(room.coroutineDispatchers.io) { - room.getEventAnnotationsSummary(eventId).toOptional() + room.relationService().getEventAnnotationsSummary(eventId).toOptional() } } fun liveTimelineEvent(eventId: String): Flow> { - return room.getTimelineEventLive(eventId).asFlow() + return room.timelineService().getTimelineEventLive(eventId).asFlow() .startWith(room.coroutineDispatchers.io) { room.getTimelineEvent(eventId).toOptional() } } fun liveStateEvent(eventType: String, stateKey: QueryStringValue): Flow> { - return room.getStateEventLive(eventType, stateKey).asFlow() + return room.stateService().getStateEventLive(eventType, stateKey).asFlow() .startWith(room.coroutineDispatchers.io) { room.getStateEvent(eventType, stateKey).toOptional() } } fun liveStateEvents(eventTypes: Set): Flow> { - return room.getStateEventsLive(eventTypes).asFlow() + return room.stateService().getStateEventsLive(eventTypes).asFlow() .startWith(room.coroutineDispatchers.io) { - room.getStateEvents(eventTypes) + room.stateService().getStateEvents(eventTypes) } } fun liveReadMarker(): Flow> { - return room.getReadMarkerLive().asFlow() + return room.readService().getReadMarkerLive().asFlow() } fun liveReadReceipt(): Flow> { - return room.getMyReadReceiptLive().asFlow() + return room.readService().getMyReadReceiptLive().asFlow() } fun liveEventReadReceipts(eventId: String): Flow> { - return room.getEventReadReceiptsLive(eventId).asFlow() + return room.readService().getEventReadReceiptsLive(eventId).asFlow() } fun liveDraft(): Flow> { - return room.getDraftLive().asFlow() + return room.draftService().getDraftLive().asFlow() .startWith(room.coroutineDispatchers.io) { - room.getDraft().toOptional() + room.draftService().getDraft().toOptional() } } fun liveNotificationState(): Flow { - return room.getLiveRoomNotificationState().asFlow() + return room.roomPushRuleService().getLiveRoomNotificationState().asFlow() } fun liveThreadSummaries(): Flow> { - return room.getAllThreadSummariesLive().asFlow() + return room.threadsService().getAllThreadSummariesLive().asFlow() .startWith(room.coroutineDispatchers.io) { - room.getAllThreadSummaries() + room.threadsService().getAllThreadSummaries() } } + fun liveThreadList(): Flow> { - return room.getAllThreadsLive().asFlow() + return room.threadsLocalService().getAllThreadsLive().asFlow() .startWith(room.coroutineDispatchers.io) { - room.getAllThreads() + room.threadsLocalService().getAllThreads() } } + fun liveLocalUnreadThreadList(): Flow> { - return room.getMarkedThreadNotificationsLive().asFlow() + return room.threadsLocalService().getMarkedThreadNotificationsLive().asFlow() .startWith(room.coroutineDispatchers.io) { - room.getMarkedThreadNotifications() + room.threadsLocalService().getMarkedThreadNotifications() } } } diff --git a/matrix-sdk-android-flow/src/main/java/org/matrix/android/sdk/flow/FlowSession.kt b/matrix-sdk-android-flow/src/main/java/org/matrix/android/sdk/flow/FlowSession.kt index 18753f0705..9f260858f6 100644 --- a/matrix-sdk-android-flow/src/main/java/org/matrix/android/sdk/flow/FlowSession.kt +++ b/matrix-sdk-android-flow/src/main/java/org/matrix/android/sdk/flow/FlowSession.kt @@ -46,16 +46,16 @@ import org.matrix.android.sdk.api.util.toOptional class FlowSession(private val session: Session) { fun liveRoomSummaries(queryParams: RoomSummaryQueryParams, sortOrder: RoomSortOrder = RoomSortOrder.NONE): Flow> { - return session.getRoomSummariesLive(queryParams, sortOrder).asFlow() + return session.roomService().getRoomSummariesLive(queryParams, sortOrder).asFlow() .startWith(session.coroutineDispatchers.io) { - session.getRoomSummaries(queryParams, sortOrder) + session.roomService().getRoomSummaries(queryParams, sortOrder) } } fun liveGroupSummaries(queryParams: GroupSummaryQueryParams): Flow> { - return session.getGroupSummariesLive(queryParams).asFlow() + return session.groupService().getGroupSummariesLive(queryParams).asFlow() .startWith(session.coroutineDispatchers.io) { - session.getGroupSummaries(queryParams) + session.groupService().getGroupSummaries(queryParams) } } @@ -67,9 +67,9 @@ class FlowSession(private val session: Session) { } fun liveBreadcrumbs(queryParams: RoomSummaryQueryParams): Flow> { - return session.getBreadcrumbsLive(queryParams).asFlow() + return session.roomService().getBreadcrumbsLive(queryParams).asFlow() .startWith(session.coroutineDispatchers.io) { - session.getBreadcrumbs(queryParams) + session.roomService().getBreadcrumbs(queryParams) } } @@ -85,43 +85,47 @@ class FlowSession(private val session: Session) { } fun livePushers(): Flow> { - return session.getPushersLive().asFlow() + return session.pushersService().getPushersLive().asFlow() } fun liveUser(userId: String): Flow> { - return session.getUserLive(userId).asFlow() + return session.userService().getUserLive(userId).asFlow() .startWith(session.coroutineDispatchers.io) { - session.getUser(userId).toOptional() + session.userService().getUser(userId).toOptional() } } fun liveRoomMember(userId: String, roomId: String): Flow> { - return session.getRoomMemberLive(userId, roomId).asFlow() + return session.roomService().getRoomMemberLive(userId, roomId).asFlow() .startWith(session.coroutineDispatchers.io) { - session.getRoomMember(userId, roomId).toOptional() + session.roomService().getRoomMember(userId, roomId).toOptional() } } fun liveUsers(): Flow> { - return session.getUsersLive().asFlow() + return session.userService().getUsersLive().asFlow() } fun liveIgnoredUsers(): Flow> { - return session.getIgnoredUsersLive().asFlow() + return session.userService().getIgnoredUsersLive().asFlow() } fun livePagedUsers(filter: String? = null, excludedUserIds: Set? = null): Flow> { - return session.getPagedUsersLive(filter, excludedUserIds).asFlow() + return session.userService().getPagedUsersLive(filter, excludedUserIds).asFlow() } fun liveThreePIds(refreshData: Boolean): Flow> { - return session.getThreePidsLive(refreshData).asFlow() - .startWith(session.coroutineDispatchers.io) { session.getThreePids() } + return session.profileService().getThreePidsLive(refreshData).asFlow() + .startWith(session.coroutineDispatchers.io) { + session.profileService().getThreePids() + } } fun livePendingThreePIds(): Flow> { - return session.getPendingThreePidsLive().asFlow() - .startWith(session.coroutineDispatchers.io) { session.getPendingThreePids() } + return session.profileService().getPendingThreePidsLive().asFlow() + .startWith(session.coroutineDispatchers.io) { + session.profileService().getPendingThreePids() + } } fun liveUserCryptoDevices(userId: String): Flow> { @@ -179,7 +183,7 @@ class FlowSession(private val session: Session) { } fun liveRoomChangeMembershipState(): Flow> { - return session.getChangeMembershipsLive().asFlow() + return session.roomService().getChangeMembershipsLive().asFlow() } } diff --git a/matrix-sdk-android/build.gradle b/matrix-sdk-android/build.gradle index e9c8804bac..ed84a73ef3 100644 --- a/matrix-sdk-android/build.gradle +++ b/matrix-sdk-android/build.gradle @@ -7,13 +7,37 @@ apply plugin: "org.jetbrains.dokka" buildscript { repositories { - mavenCentral() + // Do not use `mavenCentral()`, it prevents Dependabot from working properly + maven { + url 'https://repo1.maven.org/maven2' + } } dependencies { classpath "io.realm:realm-gradle-plugin:10.9.0" } } +dokkaHtml { + dokkaSourceSets { + configureEach { + // Emit warnings about not documented members. + // reportUndocumented.set(true) + // Suppress legacy Riot's packages. + perPackageOption { + matchingRegex.set("org.matrix.android.sdk.internal.legacy.riot") + suppress.set(true) + } + perPackageOption { + matchingRegex.set("org.matrix.androidsdk.crypto.data") + suppress.set(true) + } + // List of files with module and package documentation + // https://kotlinlang.org/docs/reference/kotlin-doc.html#module-and-package-documentation + includes.from("./docs/modules.md", "./docs/packages.md") + } + } +} + android { testOptions.unitTests.includeAndroidResources = true @@ -32,7 +56,7 @@ android { // that the app's state is completely cleared between tests. testInstrumentationRunnerArguments clearPackageData: 'true' - buildConfigField "String", "SDK_VERSION", "\"1.4.12\"" + buildConfigField "String", "SDK_VERSION", "\"1.4.18\"" buildConfigField "String", "GIT_SDK_REVISION", "\"${gitRevision()}\"" buildConfigField "String", "GIT_SDK_REVISION_UNIX_DATE", "\"${gitRevisionUnixDate()}\"" @@ -77,6 +101,9 @@ android { freeCompilerArgs += [ // Disabled for now, there are too many errors. Could be handled in another dedicated PR // '-Xexplicit-api=strict', // or warning + "-opt-in=kotlin.RequiresOptIn", + // Opt in for kotlinx.coroutines.FlowPreview + "-opt-in=kotlinx.coroutines.FlowPreview", ] } @@ -148,7 +175,7 @@ dependencies { implementation libs.androidx.work // olm lib is now hosted in MavenCentral - implementation 'org.matrix.android:olm-sdk:3.2.10' + implementation 'org.matrix.android:olm-sdk:3.2.11' // DI implementation libs.dagger.dagger @@ -165,7 +192,7 @@ dependencies { implementation libs.apache.commonsImaging // Phone number https://github.com/google/libphonenumber - implementation 'com.googlecode.libphonenumber:libphonenumber:8.12.46' + implementation 'com.googlecode.libphonenumber:libphonenumber:8.12.48' testImplementation libs.tests.junit // Note: version sticks to 1.9.2 due to https://github.com/mockk/mockk/issues/281 diff --git a/matrix-sdk-android/docs/modules.md b/matrix-sdk-android/docs/modules.md new file mode 100644 index 0000000000..fb082c1bc2 --- /dev/null +++ b/matrix-sdk-android/docs/modules.md @@ -0,0 +1,21 @@ +# Module matrix-sdk-android + + +**Note**: You are viewing the nightly documentation of the Android Matrix SDK library. The documentation of the released library can be found here: [https://matrix-org.github.io/matrix-android-sdk2/](https://matrix-org.github.io/matrix-android-sdk2/) + +## Welcome to the matrix-sdk-android documentation! + +This pages list the complete API that this SDK is exposing to a client application. + +*We are still building the documentation, so everything is not documented yet.* + +A few entry points: + +- **[Matrix](org.matrix.android.sdk.api.Matrix)**: The app will have to create and manage a **[Matrix](org.matrix.android.sdk.api.Matrix)** object. +- From this **[Matrix](org.matrix.android.sdk.api.Matrix)** object, you will be able to get various services, including the **[AuthenticationService](org.matrix.android.sdk.api.auth.AuthenticationService)**. +- With this **[AuthenticationService](org.matrix.android.sdk.api.auth.AuthenticationService)** you will be able to get an existing **[Session](org.matrix.android.sdk.api.session.Session)**, or create one using a **[LoginWizard](org.matrix.android.sdk.api.auth.login.LoginWizard)** or a **[RegistrationWizard](org.matrix.android.sdk.api.auth.registration.RegistrationWizard)**, which will finally give you a **[Session](org.matrix.android.sdk.api.session.Session)**. +- From the **[Session](org.matrix.android.sdk.api.session.Session)**, you will be able to retrieve many Services, including the **[RoomService](org.matrix.android.sdk.api.session.room.RoomService)**. +- From the **[RoomService](org.matrix.android.sdk.api.session.room.RoomService)**, you will be able to list the rooms, create a **[Room](org.matrix.android.sdk.api.session.room.Room)**, and get a specific **[Room](org.matrix.android.sdk.api.session.room.Room)**. +- And from a **[Room](org.matrix.android.sdk.api.session.room.Room)**, you will be able to do many things, including get a **[Timeline](org.matrix.android.sdk.api.session.room.timeline.Timeline)**, send messages, etc. + +Please read the whole documentation to learn more! diff --git a/matrix-sdk-android/docs/packages.md b/matrix-sdk-android/docs/packages.md new file mode 100644 index 0000000000..ae7bee1b4e --- /dev/null +++ b/matrix-sdk-android/docs/packages.md @@ -0,0 +1,3 @@ +# Package org.matrix.android.sdk.api + +This is the root package of the API exposed by this SDK. diff --git a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/PermalinkParserTest.kt b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/PermalinkParserTest.kt index b11a538949..8717b3f7a8 100644 --- a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/PermalinkParserTest.kt +++ b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/PermalinkParserTest.kt @@ -37,7 +37,10 @@ class PermalinkParserTest { Assert.assertTrue("Should be parsed as email invite but was ${parsedLink::class.java}", parsedLink is PermalinkData.RoomEmailInviteLink) parsedLink as PermalinkData.RoomEmailInviteLink Assert.assertEquals("!MRBNLPtFnMAazZVPMO:matrix.org", parsedLink.roomId) - Assert.assertEquals("XmOwRZnSFabCRhTywFbJWKXWVNPysOpXIbroMGaUymqkJSvHeVKRsjHajwjCYdBsvGSvHauxbKfJmOxtXldtyLnyBMLKpBQCMzyYggrdapbVIceWZBtmslOQrXLABRoe", parsedLink.token) + Assert.assertEquals( + "XmOwRZnSFabCRhTywFbJWKXWVNPysOpXIbroMGaUymqkJSvHeVKRsjHajwjCYdBsvGSvHauxbKfJmOxtXldtyLnyBMLKpBQCMzyYggrdapbVIceWZBtmslOQrXLABRoe", + parsedLink.token + ) Assert.assertEquals("vector.im", parsedLink.identityServer) Assert.assertEquals("Team2", parsedLink.roomName) Assert.assertEquals("hiphop5", parsedLink.inviterName) @@ -45,7 +48,8 @@ class PermalinkParserTest { @Test fun testParseLinkWIthEvent() { - val rawInvite = "https://matrix.to/#/!OGEhHVWSdvArJzumhm:matrix.org/\$xuvJUVDJnwEeVjPx029rAOZ50difpmU_5gZk_T0jGfc?via=matrix.org&via=libera.chat&via=matrix.example.io" + val rawInvite = + "https://matrix.to/#/!OGEhHVWSdvArJzumhm:matrix.org/\$xuvJUVDJnwEeVjPx029rAOZ50difpmU_5gZk_T0jGfc?via=matrix.org&via=libera.chat&via=matrix.example.io" val parsedLink = PermalinkParser.parse(rawInvite) Assert.assertTrue("Should be parsed as room link", parsedLink is PermalinkData.RoomLink) diff --git a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/SingleThreadCoroutineDispatcher.kt b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/SingleThreadCoroutineDispatcher.kt index 3e3af10799..a44cd6c80f 100644 --- a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/SingleThreadCoroutineDispatcher.kt +++ b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/SingleThreadCoroutineDispatcher.kt @@ -21,5 +21,7 @@ import kotlinx.coroutines.asCoroutineDispatcher import org.matrix.android.sdk.api.MatrixCoroutineDispatchers import java.util.concurrent.Executors -internal val testCoroutineDispatchers = MatrixCoroutineDispatchers(Main, Main, Main, Main, - Executors.newSingleThreadExecutor().asCoroutineDispatcher()) +internal val testCoroutineDispatchers = MatrixCoroutineDispatchers( + Main, Main, Main, Main, + Executors.newSingleThreadExecutor().asCoroutineDispatcher() +) diff --git a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/account/ChangePasswordTest.kt b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/account/ChangePasswordTest.kt index 933074cdce..6d740c5a34 100644 --- a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/account/ChangePasswordTest.kt +++ b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/account/ChangePasswordTest.kt @@ -46,7 +46,7 @@ class ChangePasswordTest : InstrumentedTest { // Change password commonTestHelper.runBlockingTest { - session.changePassword(TestConstants.PASSWORD, NEW_PASSWORD) + session.accountService().changePassword(TestConstants.PASSWORD, NEW_PASSWORD) } // Try to login with the previous password, it will fail diff --git a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/account/DeactivateAccountTest.kt b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/account/DeactivateAccountTest.kt index f8d108fb73..d2dfe4d945 100644 --- a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/account/DeactivateAccountTest.kt +++ b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/account/DeactivateAccountTest.kt @@ -47,7 +47,7 @@ class DeactivateAccountTest : InstrumentedTest { // Deactivate the account commonTestHelper.runBlockingTest { - session.deactivateAccount( + session.accountService().deactivateAccount( eraseAllData = false, userInteractiveAuthInterceptor = object : UserInteractiveAuthInterceptor { override fun performStage(flowResponse: RegistrationFlowResponse, errCode: String?, promise: Continuation) { @@ -67,9 +67,11 @@ class DeactivateAccountTest : InstrumentedTest { val throwable = commonTestHelper.logAccountWithError(session.myUserId, TestConstants.PASSWORD) // Test the error - assertTrue(throwable is Failure.ServerError && - throwable.error.code == MatrixError.M_USER_DEACTIVATED && - throwable.error.message == "This account has been deactivated") + assertTrue( + throwable is Failure.ServerError && + throwable.error.code == MatrixError.M_USER_DEACTIVATED && + throwable.error.message == "This account has been deactivated" + ) // Try to create an account with the deactivate account user id, it will fail (M_USER_IN_USE) val hs = commonTestHelper.createHomeServerConfig() @@ -95,8 +97,10 @@ class DeactivateAccountTest : InstrumentedTest { // Test the error accountCreationError.let { - assertTrue(it is Failure.ServerError && - it.error.code == MatrixError.M_USER_IN_USE) + assertTrue( + it is Failure.ServerError && + it.error.code == MatrixError.M_USER_IN_USE + ) } // No need to close the session, it has been deactivated diff --git a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/common/CommonTestHelper.kt b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/common/CommonTestHelper.kt index ac4ccf56d1..6678b109a6 100644 --- a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/common/CommonTestHelper.kt +++ b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/common/CommonTestHelper.kt @@ -57,13 +57,15 @@ import java.util.concurrent.TimeUnit class CommonTestHelper(context: Context) { internal val matrix: TestMatrix - val coroutineScope = CoroutineScope(SupervisorJob() + Dispatchers.Main) + private val coroutineScope = CoroutineScope(SupervisorJob() + Dispatchers.Main) + private var accountNumber = 0 fun getTestInterceptor(session: Session): MockOkHttpInterceptor? = TestModule.interceptorForSession(session.sessionId) as? MockOkHttpInterceptor init { + var _matrix: TestMatrix? = null UiThreadStatement.runOnUiThread { - TestMatrix.initialize( + _matrix = TestMatrix( context, MatrixConfiguration( applicationFlavor = "TestFlavor", @@ -71,7 +73,7 @@ class CommonTestHelper(context: Context) { ) ) } - matrix = TestMatrix.getInstance() + matrix = _matrix!! } fun createAccount(userNamePrefix: String, testParams: SessionTestParams): Session { @@ -145,7 +147,7 @@ class CommonTestHelper(context: Context) { * @param nbOfMessages the number of time the message will be sent */ fun sendTextMessage(room: Room, message: String, nbOfMessages: Int, timeout: Long = TestConstants.timeOutMillis): List { - val timeline = room.createTimeline(null, TimelineSettings(10)) + val timeline = room.timelineService().createTimeline(null, TimelineSettings(10)) timeline.start() val sentEvents = sendTextMessagesBatched(timeline, room, message, nbOfMessages, timeout) timeline.dispose() @@ -165,11 +167,12 @@ class CommonTestHelper(context: Context) { .forEach { batchedMessages -> batchedMessages.forEach { formattedMessage -> if (rootThreadEventId != null) { - room.replyInThread( + room.relationService().replyInThread( rootThreadEventId = rootThreadEventId, - replyInThreadText = formattedMessage) + replyInThreadText = formattedMessage + ) } else { - room.sendTextMessage(formattedMessage) + room.sendService().sendTextMessage(formattedMessage) } } waitWithLatch(timeout) { latch -> @@ -214,7 +217,7 @@ class CommonTestHelper(context: Context) { numberOfMessages: Int, rootThreadEventId: String, timeout: Long = TestConstants.timeOutMillis): List { - val timeline = room.createTimeline(null, TimelineSettings(10)) + val timeline = room.timelineService().createTimeline(null, TimelineSettings(10)) timeline.start() val sentEvents = sendTextMessagesBatched(timeline, room, message, numberOfMessages, timeout, rootThreadEventId) timeline.dispose() @@ -237,7 +240,7 @@ class CommonTestHelper(context: Context) { password: String, testParams: SessionTestParams): Session { val session = createAccountAndSync( - userNamePrefix + "_" + System.currentTimeMillis() + UUID.randomUUID(), + userNamePrefix + "_" + accountNumber++ + "_" + UUID.randomUUID(), password, testParams ) @@ -294,7 +297,7 @@ class CommonTestHelper(context: Context) { val session = (registrationResult as RegistrationResult.Success).session session.open() if (sessionTestParams.withInitialSync) { - syncSession(session, 60_000) + syncSession(session, 120_000) } return session } @@ -375,7 +378,10 @@ class CommonTestHelper(context: Context) { * @throws InterruptedException */ fun await(latch: CountDownLatch, timeout: Long? = TestConstants.timeOutMillis) { - assertTrue(latch.await(timeout ?: TestConstants.timeOutMillis, TimeUnit.MILLISECONDS)) + assertTrue( + "Timed out after " + timeout + "ms waiting for something to happen. See stacktrace for cause.", + latch.await(timeout ?: TestConstants.timeOutMillis, TimeUnit.MILLISECONDS) + ) } suspend fun retryPeriodicallyWithLatch(latch: CountDownLatch, condition: (() -> Boolean)) { @@ -431,7 +437,7 @@ class CommonTestHelper(context: Context) { fun signOutAndClose(session: Session) { runBlockingTest(timeout = 60_000) { - session.signOut(true) + session.signOutService().signOut(true) } // no need signout will close // session.close() diff --git a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/common/CryptoTestHelper.kt b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/common/CryptoTestHelper.kt index e3ab1a4921..348841313b 100644 --- a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/common/CryptoTestHelper.kt +++ b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/common/CryptoTestHelper.kt @@ -19,6 +19,7 @@ package org.matrix.android.sdk.common import android.os.SystemClock import android.util.Log import androidx.lifecycle.Observer +import org.amshove.kluent.fail import org.junit.Assert.assertEquals import org.junit.Assert.assertNotNull import org.junit.Assert.assertNull @@ -31,8 +32,16 @@ import org.matrix.android.sdk.api.crypto.MXCRYPTO_ALGORITHM_MEGOLM import org.matrix.android.sdk.api.crypto.MXCRYPTO_ALGORITHM_MEGOLM_BACKUP import org.matrix.android.sdk.api.extensions.orFalse import org.matrix.android.sdk.api.session.Session +import org.matrix.android.sdk.api.session.crypto.MXCryptoError +import org.matrix.android.sdk.api.session.crypto.crosssigning.KEYBACKUP_SECRET_SSSS_NAME +import org.matrix.android.sdk.api.session.crypto.crosssigning.MASTER_KEY_SSSS_NAME +import org.matrix.android.sdk.api.session.crypto.crosssigning.SELF_SIGNING_KEY_SSSS_NAME +import org.matrix.android.sdk.api.session.crypto.crosssigning.USER_SIGNING_KEY_SSSS_NAME +import org.matrix.android.sdk.api.session.crypto.keysbackup.KeysVersion import org.matrix.android.sdk.api.session.crypto.keysbackup.MegolmBackupAuthData import org.matrix.android.sdk.api.session.crypto.keysbackup.MegolmBackupCreationInfo +import org.matrix.android.sdk.api.session.crypto.keysbackup.extractCurveKeyFromRecoveryKey +import org.matrix.android.sdk.api.session.crypto.model.OlmDecryptionResult import org.matrix.android.sdk.api.session.crypto.verification.IncomingSasVerificationTransaction import org.matrix.android.sdk.api.session.crypto.verification.OutgoingSasVerificationTransaction import org.matrix.android.sdk.api.session.crypto.verification.VerificationMethod @@ -40,12 +49,19 @@ import org.matrix.android.sdk.api.session.crypto.verification.VerificationTxStat import org.matrix.android.sdk.api.session.events.model.Event import org.matrix.android.sdk.api.session.events.model.EventType import org.matrix.android.sdk.api.session.events.model.toContent +import org.matrix.android.sdk.api.session.events.model.toModel +import org.matrix.android.sdk.api.session.getRoom import org.matrix.android.sdk.api.session.room.Room import org.matrix.android.sdk.api.session.room.model.Membership import org.matrix.android.sdk.api.session.room.model.RoomSummary import org.matrix.android.sdk.api.session.room.model.create.CreateRoomParams +import org.matrix.android.sdk.api.session.room.model.message.MessageContent import org.matrix.android.sdk.api.session.room.roomSummaryQueryParams +import org.matrix.android.sdk.api.session.securestorage.EmptyKeySigner +import org.matrix.android.sdk.api.session.securestorage.SharedSecretStorageService import org.matrix.android.sdk.api.util.Optional +import org.matrix.android.sdk.api.util.awaitCallback +import org.matrix.android.sdk.api.util.toBase64NoPadding import java.util.UUID import kotlin.coroutines.Continuation import kotlin.coroutines.resume @@ -64,12 +80,12 @@ class CryptoTestHelper(private val testHelper: CommonTestHelper) { val aliceSession = testHelper.createAccount(TestConstants.USER_ALICE, defaultSessionParams) val roomId = testHelper.runBlockingTest { - aliceSession.createRoom(CreateRoomParams().apply { name = "MyRoom" }) + aliceSession.roomService().createRoom(CreateRoomParams().apply { name = "MyRoom" }) } if (encryptedRoom) { testHelper.waitWithLatch { latch -> val room = aliceSession.getRoom(roomId)!! - room.enableEncryption() + room.roomCryptoService().enableEncryption() val roomSummaryLive = room.getRoomSummaryLive() val roomSummaryObserver = object : Observer> { override fun onChanged(roomSummary: Optional) { @@ -98,7 +114,7 @@ class CryptoTestHelper(private val testHelper: CommonTestHelper) { val bobSession = testHelper.createAccount(TestConstants.USER_BOB, defaultSessionParams) testHelper.waitWithLatch { latch -> - val bobRoomSummariesLive = bobSession.getRoomSummariesLive(roomSummaryQueryParams { }) + val bobRoomSummariesLive = bobSession.roomService().getRoomSummariesLive(roomSummaryQueryParams { }) val newRoomObserver = object : Observer> { override fun onChanged(t: List?) { if (t?.isNotEmpty() == true) { @@ -108,14 +124,15 @@ class CryptoTestHelper(private val testHelper: CommonTestHelper) { } } bobRoomSummariesLive.observeForever(newRoomObserver) - aliceRoom.invite(bobSession.myUserId) + aliceRoom.membershipService().invite(bobSession.myUserId) } testHelper.waitWithLatch { latch -> - val bobRoomSummariesLive = bobSession.getRoomSummariesLive(roomSummaryQueryParams { }) + val bobRoomSummariesLive = bobSession.roomService().getRoomSummariesLive(roomSummaryQueryParams { }) val roomJoinedObserver = object : Observer> { override fun onChanged(t: List?) { if (bobSession.getRoom(aliceRoomId) + ?.membershipService() ?.getRoomMember(bobSession.myUserId) ?.membership == Membership.JOIN) { bobRoomSummariesLive.removeObserver(this) @@ -124,7 +141,7 @@ class CryptoTestHelper(private val testHelper: CommonTestHelper) { } } bobRoomSummariesLive.observeForever(roomJoinedObserver) - bobSession.joinRoom(aliceRoomId) + bobSession.roomService().joinRoom(aliceRoomId) } // Ensure bob can send messages to the room // val roomFromBobPOV = bobSession.getRoom(aliceRoomId)!! @@ -160,11 +177,11 @@ class CryptoTestHelper(private val testHelper: CommonTestHelper) { val samSession = testHelper.createAccount(TestConstants.USER_SAM, defaultSessionParams) testHelper.runBlockingTest { - room.invite(samSession.myUserId, null) + room.membershipService().invite(samSession.myUserId, null) } testHelper.runBlockingTest { - samSession.joinRoom(room.roomId, null, emptyList()) + samSession.roomService().joinRoom(room.roomId, null, emptyList()) } return samSession @@ -186,17 +203,49 @@ class CryptoTestHelper(private val testHelper: CommonTestHelper) { val roomFromAlicePOV = aliceSession.getRoom(aliceRoomId)!! // Alice sends a message - testHelper.sendTextMessage(roomFromAlicePOV, messagesFromAlice[0], 1) + testHelper.sendTextMessage(roomFromAlicePOV, messagesFromAlice[0], 1).first().eventId.let { sentEventId -> + // ensure bob got it + ensureEventReceived(aliceRoomId, sentEventId, bobSession, true) + } // Bob send 3 messages - testHelper.sendTextMessage(roomFromBobPOV, messagesFromBob[0], 1) - testHelper.sendTextMessage(roomFromBobPOV, messagesFromBob[1], 1) - testHelper.sendTextMessage(roomFromBobPOV, messagesFromBob[2], 1) + testHelper.sendTextMessage(roomFromBobPOV, messagesFromBob[0], 1).first().eventId.let { sentEventId -> + // ensure alice got it + ensureEventReceived(aliceRoomId, sentEventId, aliceSession, true) + } + + testHelper.sendTextMessage(roomFromBobPOV, messagesFromBob[1], 1).first().eventId.let { sentEventId -> + // ensure alice got it + ensureEventReceived(aliceRoomId, sentEventId, aliceSession, true) + } + testHelper.sendTextMessage(roomFromBobPOV, messagesFromBob[2], 1).first().eventId.let { sentEventId -> + // ensure alice got it + ensureEventReceived(aliceRoomId, sentEventId, aliceSession, true) + } + // Alice sends a message - testHelper.sendTextMessage(roomFromAlicePOV, messagesFromAlice[1], 1) + testHelper.sendTextMessage(roomFromAlicePOV, messagesFromAlice[1], 1).first().eventId.let { sentEventId -> + // ensure bob got it + ensureEventReceived(aliceRoomId, sentEventId, bobSession, true) + } return cryptoTestData } + private fun ensureEventReceived(roomId: String, eventId: String, session: Session, andCanDecrypt: Boolean) { + testHelper.waitWithLatch { latch -> + testHelper.retryPeriodicallyWithLatch(latch) { + val timeLineEvent = session.getRoom(roomId)?.timelineService()?.getTimelineEvent(eventId) + if (andCanDecrypt) { + timeLineEvent != null && + timeLineEvent.isEncrypted() && + timeLineEvent.root.getClearType() == EventType.MESSAGE + } else { + timeLineEvent != null + } + } + } + } + fun checkEncryptedEvent(event: Event, roomId: String, clearMessage: String, senderSession: Session) { assertEquals(EventType.ENCRYPTED, event.type) assertNotNull(event.content) @@ -242,8 +291,8 @@ class CryptoTestHelper(private val testHelper: CommonTestHelper) { fun createDM(alice: Session, bob: Session): String { var roomId: String = "" testHelper.waitWithLatch { latch -> - roomId = alice.createDirectRoom(bob.myUserId) - val bobRoomSummariesLive = bob.getRoomSummariesLive(roomSummaryQueryParams { }) + roomId = alice.roomService().createDirectRoom(bob.myUserId) + val bobRoomSummariesLive = bob.roomService().getRoomSummariesLive(roomSummaryQueryParams { }) val newRoomObserver = object : Observer> { override fun onChanged(t: List?) { if (t?.any { it.roomId == roomId }.orFalse()) { @@ -256,10 +305,11 @@ class CryptoTestHelper(private val testHelper: CommonTestHelper) { } testHelper.waitWithLatch { latch -> - val bobRoomSummariesLive = bob.getRoomSummariesLive(roomSummaryQueryParams { }) + val bobRoomSummariesLive = bob.roomService().getRoomSummariesLive(roomSummaryQueryParams { }) val newRoomObserver = object : Observer> { override fun onChanged(t: List?) { if (bob.getRoom(roomId) + ?.membershipService() ?.getRoomMember(bob.myUserId) ?.membership == Membership.JOIN) { bobRoomSummariesLive.removeObserver(this) @@ -268,7 +318,7 @@ class CryptoTestHelper(private val testHelper: CommonTestHelper) { } } bobRoomSummariesLive.observeForever(newRoomObserver) - bob.joinRoom(roomId) + bob.roomService().joinRoom(roomId) } return roomId @@ -288,7 +338,61 @@ class CryptoTestHelper(private val testHelper: CommonTestHelper) { ) ) } - }, it) + }, it + ) + } + } + + /** + * Initialize cross-signing, set up megolm backup and save all in 4S + */ + fun bootstrapSecurity(session: Session) { + initializeCrossSigning(session) + val ssssService = session.sharedSecretStorageService() + testHelper.runBlockingTest { + val keyInfo = ssssService.generateKey( + UUID.randomUUID().toString(), + null, + "ssss_key", + EmptyKeySigner() + ) + ssssService.setDefaultKey(keyInfo.keyId) + + ssssService.storeSecret( + MASTER_KEY_SSSS_NAME, + session.cryptoService().crossSigningService().getCrossSigningPrivateKeys()!!.master!!, + listOf(SharedSecretStorageService.KeyRef(keyInfo.keyId, keyInfo.keySpec)) + ) + + ssssService.storeSecret( + SELF_SIGNING_KEY_SSSS_NAME, + session.cryptoService().crossSigningService().getCrossSigningPrivateKeys()!!.selfSigned!!, + listOf(SharedSecretStorageService.KeyRef(keyInfo.keyId, keyInfo.keySpec)) + ) + + ssssService.storeSecret( + USER_SIGNING_KEY_SSSS_NAME, + session.cryptoService().crossSigningService().getCrossSigningPrivateKeys()!!.user!!, + listOf(SharedSecretStorageService.KeyRef(keyInfo.keyId, keyInfo.keySpec)) + ) + + // set up megolm backup + val creationInfo = awaitCallback { + session.cryptoService().keysBackupService().prepareKeysBackupVersion(null, null, it) + } + val version = awaitCallback { + session.cryptoService().keysBackupService().createKeysBackupVersion(creationInfo, it) + } + // Save it for gossiping + session.cryptoService().keysBackupService().saveBackupRecoveryKey(creationInfo.recoveryKey, version = version.version) + + extractCurveKeyFromRecoveryKey(creationInfo.recoveryKey)?.toBase64NoPadding()?.let { secret -> + ssssService.storeSecret( + KEYBACKUP_SECRET_SSSS_NAME, + secret, + listOf(SharedSecretStorageService.KeyRef(keyInfo.keyId, keyInfo.keySpec)) + ) + } } } @@ -296,28 +400,37 @@ class CryptoTestHelper(private val testHelper: CommonTestHelper) { assertTrue(alice.cryptoService().crossSigningService().canCrossSign()) assertTrue(bob.cryptoService().crossSigningService().canCrossSign()) - val requestID = UUID.randomUUID().toString() val aliceVerificationService = alice.cryptoService().verificationService() val bobVerificationService = bob.cryptoService().verificationService() - aliceVerificationService.beginKeyVerificationInDMs( - VerificationMethod.SAS, - requestID, - roomId, - bob.myUserId, - bob.sessionParams.credentials.deviceId!!) + val localId = UUID.randomUUID().toString() + aliceVerificationService.requestKeyVerificationInDMs( + localId = localId, + methods = listOf(VerificationMethod.SAS, VerificationMethod.QR_CODE_SCAN, VerificationMethod.QR_CODE_SHOW), + otherUserId = bob.myUserId, + roomId = roomId + ).transactionId - // we should reach SHOW SAS on both - var alicePovTx: OutgoingSasVerificationTransaction? = null - var bobPovTx: IncomingSasVerificationTransaction? = null - - // wait for alice to get the ready testHelper.waitWithLatch { testHelper.retryPeriodicallyWithLatch(it) { - bobPovTx = bobVerificationService.getExistingTransaction(alice.myUserId, requestID) as? IncomingSasVerificationTransaction - Log.v("TEST", "== bobPovTx is ${alicePovTx?.uxState}") - if (bobPovTx?.state == VerificationTxState.OnStarted) { - bobPovTx?.performAccept() + bobVerificationService.getExistingVerificationRequests(alice.myUserId).firstOrNull { + it.requestInfo?.fromDevice == alice.sessionParams.deviceId + } != null + } + } + val incomingRequest = bobVerificationService.getExistingVerificationRequests(alice.myUserId).first { + it.requestInfo?.fromDevice == alice.sessionParams.deviceId + } + bobVerificationService.readyPendingVerification(listOf(VerificationMethod.SAS), alice.myUserId, incomingRequest.transactionId!!) + + var requestID: String? = null + // wait for it to be readied + testHelper.waitWithLatch { + testHelper.retryPeriodicallyWithLatch(it) { + val outgoingRequest = aliceVerificationService.getExistingVerificationRequests(bob.myUserId) + .firstOrNull { it.localId == localId } + if (outgoingRequest?.isReady == true) { + requestID = outgoingRequest.transactionId!! true } else { false @@ -325,9 +438,21 @@ class CryptoTestHelper(private val testHelper: CommonTestHelper) { } } + aliceVerificationService.beginKeyVerificationInDMs( + VerificationMethod.SAS, + requestID!!, + roomId, + bob.myUserId, + bob.sessionParams.credentials.deviceId!! + ) + + // we should reach SHOW SAS on both + var alicePovTx: OutgoingSasVerificationTransaction? = null + var bobPovTx: IncomingSasVerificationTransaction? = null + testHelper.waitWithLatch { testHelper.retryPeriodicallyWithLatch(it) { - alicePovTx = aliceVerificationService.getExistingTransaction(bob.myUserId, requestID) as? OutgoingSasVerificationTransaction + alicePovTx = aliceVerificationService.getExistingTransaction(bob.myUserId, requestID!!) as? OutgoingSasVerificationTransaction Log.v("TEST", "== alicePovTx is ${alicePovTx?.uxState}") alicePovTx?.state == VerificationTxState.ShortCodeReady } @@ -335,7 +460,7 @@ class CryptoTestHelper(private val testHelper: CommonTestHelper) { // wait for alice to get the ready testHelper.waitWithLatch { testHelper.retryPeriodicallyWithLatch(it) { - bobPovTx = bobVerificationService.getExistingTransaction(alice.myUserId, requestID) as? IncomingSasVerificationTransaction + bobPovTx = bobVerificationService.getExistingTransaction(alice.myUserId, requestID!!) as? IncomingSasVerificationTransaction Log.v("TEST", "== bobPovTx is ${alicePovTx?.uxState}") if (bobPovTx?.state == VerificationTxState.OnStarted) { bobPovTx?.performAccept() @@ -367,24 +492,70 @@ class CryptoTestHelper(private val testHelper: CommonTestHelper) { aliceSession.cryptoService().setWarnOnUnknownDevices(false) val roomId = testHelper.runBlockingTest { - aliceSession.createRoom(CreateRoomParams().apply { name = "MyRoom" }) + aliceSession.roomService().createRoom(CreateRoomParams().apply { name = "MyRoom" }) } val room = aliceSession.getRoom(roomId)!! testHelper.runBlockingTest { - room.enableEncryption() + room.roomCryptoService().enableEncryption() } val sessions = mutableListOf(aliceSession) for (index in 1 until numberOfMembers) { val session = testHelper.createAccount("User_$index", defaultSessionParams) - testHelper.runBlockingTest(timeout = 600_000) { room.invite(session.myUserId, null) } + testHelper.runBlockingTest(timeout = 600_000) { room.membershipService().invite(session.myUserId, null) } println("TEST -> " + session.myUserId + " invited") - testHelper.runBlockingTest { session.joinRoom(room.roomId, null, emptyList()) } + testHelper.runBlockingTest { session.roomService().joinRoom(room.roomId, null, emptyList()) } println("TEST -> " + session.myUserId + " joined") sessions.add(session) } return CryptoTestData(roomId, sessions) } + + fun ensureCanDecrypt(sentEventIds: List, session: Session, e2eRoomID: String, messagesText: List) { + sentEventIds.forEachIndexed { index, sentEventId -> + testHelper.waitWithLatch { latch -> + testHelper.retryPeriodicallyWithLatch(latch) { + val event = session.getRoom(e2eRoomID)!!.timelineService().getTimelineEvent(sentEventId)!!.root + testHelper.runBlockingTest { + try { + session.cryptoService().decryptEvent(event, "").let { result -> + event.mxDecryptionResult = OlmDecryptionResult( + payload = result.clearEvent, + senderKey = result.senderCurve25519Key, + keysClaimed = result.claimedEd25519Key?.let { mapOf("ed25519" to it) }, + forwardingCurve25519KeyChain = result.forwardingCurve25519KeyChain + ) + } + } catch (error: MXCryptoError) { + // nop + } + } + Log.v("TEST", "ensureCanDecrypt ${event.getClearType()} is ${event.getClearContent()}") + event.getClearType() == EventType.MESSAGE && + messagesText[index] == event.getClearContent()?.toModel()?.body + } + } + } + } + + fun ensureCannotDecrypt(sentEventIds: List, session: Session, e2eRoomID: String, expectedError: MXCryptoError.ErrorType? = null) { + sentEventIds.forEach { sentEventId -> + val event = session.getRoom(e2eRoomID)!!.timelineService().getTimelineEvent(sentEventId)!!.root + testHelper.runBlockingTest { + try { + session.cryptoService().decryptEvent(event, "") + fail("Should not be able to decrypt event") + } catch (error: MXCryptoError) { + val errorType = (error as? MXCryptoError.Base)?.errorType + if (expectedError == null) { + assertNotNull(errorType) + } else { + assertEquals("Unexpected reason", expectedError, errorType) + } + } + } + } + } } diff --git a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/common/TestConstants.kt b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/common/TestConstants.kt index 0f79896b2c..89c965c31a 100644 --- a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/common/TestConstants.kt +++ b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/common/TestConstants.kt @@ -23,7 +23,7 @@ object TestConstants { const val TESTS_HOME_SERVER_URL = "http://10.0.2.2:8080" // Time out to use when waiting for server response. - private const val AWAIT_TIME_OUT_MILLIS = 60_000 + private const val AWAIT_TIME_OUT_MILLIS = 120_000 // Time out to use when waiting for server response, when the debugger is connected. 10 minutes private const val AWAIT_TIME_OUT_WITH_DEBUGGER_MILLIS = 10 * 60_000 diff --git a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/common/TestMatrix.kt b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/common/TestMatrix.kt index fa44167a8f..5864a801e6 100644 --- a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/common/TestMatrix.kt +++ b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/common/TestMatrix.kt @@ -21,7 +21,8 @@ import android.os.Handler import android.os.Looper import androidx.lifecycle.ProcessLifecycleOwner import androidx.work.Configuration -import androidx.work.WorkManager +import androidx.work.impl.WorkManagerImpl +import androidx.work.impl.utils.taskexecutor.WorkManagerTaskExecutor import com.zhuinden.monarchy.Monarchy import org.matrix.android.sdk.BuildConfig import org.matrix.android.sdk.api.MatrixConfiguration @@ -38,13 +39,12 @@ import org.matrix.android.sdk.internal.util.BackgroundDetectionObserver import org.matrix.android.sdk.internal.worker.MatrixWorkerFactory import org.matrix.olm.OlmManager import java.util.concurrent.Executors -import java.util.concurrent.atomic.AtomicBoolean import javax.inject.Inject /** * This mimics the Matrix class but using TestMatrixComponent internally instead of regular MatrixComponent. */ -internal class TestMatrix constructor(context: Context, matrixConfiguration: MatrixConfiguration) { +internal class TestMatrix(context: Context, matrixConfiguration: MatrixConfiguration) { @Inject internal lateinit var legacySessionImporter: LegacySessionImporter @Inject internal lateinit var authenticationService: AuthenticationService @@ -60,13 +60,19 @@ internal class TestMatrix constructor(context: Context, matrixConfiguration: Mat private val uiHandler = Handler(Looper.getMainLooper()) init { - Monarchy.init(context) - DaggerTestMatrixComponent.factory().create(context, matrixConfiguration).inject(this) + val appContext = context.applicationContext + Monarchy.init(appContext) + DaggerTestMatrixComponent.factory().create(appContext, matrixConfiguration).inject(this) val configuration = Configuration.Builder() .setExecutor(Executors.newCachedThreadPool()) .setWorkerFactory(matrixWorkerFactory) .build() - WorkManager.initialize(context, configuration) + val delegate = WorkManagerImpl( + context, + configuration, + WorkManagerTaskExecutor(configuration.taskExecutor) + ) + WorkManagerImpl.setDelegate(delegate) uiHandler.post { ProcessLifecycleOwner.get().lifecycle.addObserver(backgroundDetectionObserver) } @@ -95,23 +101,6 @@ internal class TestMatrix constructor(context: Context, matrixConfiguration: Mat } companion object { - - private lateinit var instance: TestMatrix - private val isInit = AtomicBoolean(false) - - fun initialize(context: Context, matrixConfiguration: MatrixConfiguration) { - if (isInit.compareAndSet(false, true)) { - instance = TestMatrix(context.applicationContext, matrixConfiguration) - } - } - - fun getInstance(): TestMatrix { - if (isInit.compareAndSet(false, false)) { - throw IllegalStateException("Matrix is not initialized properly. You should call TestMatrix.initialize first") - } - return instance - } - fun getSdkVersion(): String { return BuildConfig.SDK_VERSION + " (" + BuildConfig.GIT_SDK_REVISION + ")" } diff --git a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/common/TestMatrixComponent.kt b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/common/TestMatrixComponent.kt index dc58339498..525e168cf1 100644 --- a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/common/TestMatrixComponent.kt +++ b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/common/TestMatrixComponent.kt @@ -29,15 +29,17 @@ import org.matrix.android.sdk.internal.raw.RawModule import org.matrix.android.sdk.internal.settings.SettingsModule import org.matrix.android.sdk.internal.util.system.SystemModule -@Component(modules = [ - TestModule::class, - MatrixModule::class, - NetworkModule::class, - AuthModule::class, - RawModule::class, - SettingsModule::class, - SystemModule::class -]) +@Component( + modules = [ + TestModule::class, + MatrixModule::class, + NetworkModule::class, + AuthModule::class, + RawModule::class, + SettingsModule::class, + SystemModule::class + ] +) @MatrixScope internal interface TestMatrixComponent : MatrixComponent { diff --git a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/AttachmentEncryptionTest.kt b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/AttachmentEncryptionTest.kt index 732f4f7dce..f5f585a1e0 100644 --- a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/AttachmentEncryptionTest.kt +++ b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/AttachmentEncryptionTest.kt @@ -29,8 +29,10 @@ import org.matrix.android.sdk.api.session.crypto.attachments.toElementToDecrypt import org.matrix.android.sdk.api.session.crypto.model.EncryptedFileInfo import org.matrix.android.sdk.api.session.crypto.model.EncryptedFileKey import org.matrix.android.sdk.internal.crypto.attachments.MXEncryptedAttachments +import org.matrix.android.sdk.internal.util.time.DefaultClock import java.io.ByteArrayOutputStream import java.io.InputStream +import java.util.UUID /** * Unit tests AttachmentEncryptionTest. @@ -48,13 +50,18 @@ class AttachmentEncryptionTest { inputStream = if (inputAsByteArray.isEmpty()) { inputAsByteArray.inputStream() } else { - val memoryFile = MemoryFile("file" + System.currentTimeMillis(), inputAsByteArray.size) + val memoryFile = MemoryFile("file_" + UUID.randomUUID(), inputAsByteArray.size) memoryFile.outputStream.write(inputAsByteArray) memoryFile.inputStream } val decryptedStream = ByteArrayOutputStream() - val result = MXEncryptedAttachments.decryptAttachment(inputStream, encryptedFileInfo.toElementToDecrypt()!!, decryptedStream) + val result = MXEncryptedAttachments.decryptAttachment( + attachmentStream = inputStream, + elementToDecrypt = encryptedFileInfo.toElementToDecrypt()!!, + outputStream = decryptedStream, + clock = DefaultClock() + ) assert(result) @@ -117,9 +124,13 @@ class AttachmentEncryptionTest { url = "dummyUrl" ) - assertEquals("YWxwaGFudW1lcmljYWxseWFscGhhbnVtZXJpY2FsbHlhbHBoYW51bWVyaWNhbGx5YWxwaGFudW1lcmljYWxseQ", - checkDecryption("zhtFStAeFx0s+9L/sSQO+WQMtldqYEHqTxMduJrCIpnkyer09kxJJuA4K+adQE4w+7jZe/vR9kIcqj9rOhDR8Q", - encryptedFileInfo)) + assertEquals( + "YWxwaGFudW1lcmljYWxseWFscGhhbnVtZXJpY2FsbHlhbHBoYW51bWVyaWNhbGx5YWxwaGFudW1lcmljYWxseQ", + checkDecryption( + "zhtFStAeFx0s+9L/sSQO+WQMtldqYEHqTxMduJrCIpnkyer09kxJJuA4K+adQE4w+7jZe/vR9kIcqj9rOhDR8Q", + encryptedFileInfo + ) + ) } @Test @@ -138,8 +149,12 @@ class AttachmentEncryptionTest { url = "dummyUrl" ) - assertNotEquals("YWxwaGFudW1lcmljYWxseWFscGhhbnVtZXJpY2FsbHlhbHBoYW51bWVyaWNhbGx5YWxwaGFudW1lcmljYWxseQ", - checkDecryption("tJVNBVJ/vl36UQt4Y5e5m84bRUrQHhcdLPvS/7EkDvlkDLZXamBB6k8THbiawiKZ5Mnq9PZMSSbgOCvmnUBOMA", - encryptedFileInfo)) + assertNotEquals( + "YWxwaGFudW1lcmljYWxseWFscGhhbnVtZXJpY2FsbHlhbHBoYW51bWVyaWNhbGx5YWxwaGFudW1lcmljYWxseQ", + checkDecryption( + "tJVNBVJ/vl36UQt4Y5e5m84bRUrQHhcdLPvS/7EkDvlkDLZXamBB6k8THbiawiKZ5Mnq9PZMSSbgOCvmnUBOMA", + encryptedFileInfo + ) + ) } } diff --git a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/CryptoStoreHelper.kt b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/CryptoStoreHelper.kt index c717c8e33f..ba1afd4758 100644 --- a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/CryptoStoreHelper.kt +++ b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/CryptoStoreHelper.kt @@ -22,6 +22,7 @@ import org.matrix.android.sdk.internal.crypto.store.db.RealmCryptoStore import org.matrix.android.sdk.internal.crypto.store.db.RealmCryptoStoreModule import org.matrix.android.sdk.internal.crypto.store.db.mapper.CrossSigningKeysMapper import org.matrix.android.sdk.internal.di.MoshiProvider +import org.matrix.android.sdk.internal.util.time.DefaultClock import kotlin.random.Random internal class CryptoStoreHelper { @@ -34,7 +35,8 @@ internal class CryptoStoreHelper { .build(), crossSigningKeysMapper = CrossSigningKeysMapper(MoshiProvider.providesMoshi()), userId = "userId_" + Random.nextInt(), - deviceId = "deviceId_sample" + deviceId = "deviceId_sample", + clock = DefaultClock(), ) } } diff --git a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/CryptoStoreTest.kt b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/CryptoStoreTest.kt index f43c425cc9..cd6c146f03 100644 --- a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/CryptoStoreTest.kt +++ b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/CryptoStoreTest.kt @@ -22,11 +22,14 @@ import org.junit.Assert.assertEquals import org.junit.Assert.assertNotEquals import org.junit.Assert.assertNull import org.junit.Before +import org.junit.Rule import org.junit.Test import org.junit.runner.RunWith import org.matrix.android.sdk.InstrumentedTest +import org.matrix.android.sdk.common.RetryTestRule import org.matrix.android.sdk.internal.crypto.model.OlmSessionWrapper import org.matrix.android.sdk.internal.crypto.store.IMXCryptoStore +import org.matrix.android.sdk.internal.util.time.DefaultClock import org.matrix.olm.OlmAccount import org.matrix.olm.OlmManager import org.matrix.olm.OlmSession @@ -36,7 +39,10 @@ private const val DUMMY_DEVICE_KEY = "DeviceKey" @RunWith(AndroidJUnit4::class) class CryptoStoreTest : InstrumentedTest { + @get:Rule val rule = RetryTestRule(3) + private val cryptoStoreHelper = CryptoStoreHelper() + private val clock = DefaultClock() @Before fun setup() { @@ -74,9 +80,11 @@ class CryptoStoreTest : InstrumentedTest { } val olmSession1 = OlmSession().apply { - initOutboundSession(olmAccount1, + initOutboundSession( + olmAccount1, olmAccount1.identityKeys()[OlmAccount.JSON_KEY_IDENTITY_KEY], - olmAccount1.oneTimeKeys()[OlmAccount.JSON_KEY_ONE_TIME_KEY]?.values?.first()) + olmAccount1.oneTimeKeys()[OlmAccount.JSON_KEY_ONE_TIME_KEY]?.values?.first() + ) } val sessionId1 = olmSession1.sessionIdentifier() @@ -91,9 +99,11 @@ class CryptoStoreTest : InstrumentedTest { } val olmSession2 = OlmSession().apply { - initOutboundSession(olmAccount2, + initOutboundSession( + olmAccount2, olmAccount2.identityKeys()[OlmAccount.JSON_KEY_IDENTITY_KEY], - olmAccount2.oneTimeKeys()[OlmAccount.JSON_KEY_ONE_TIME_KEY]?.values?.first()) + olmAccount2.oneTimeKeys()[OlmAccount.JSON_KEY_ONE_TIME_KEY]?.values?.first() + ) } val sessionId2 = olmSession2.sessionIdentifier() @@ -106,7 +116,7 @@ class CryptoStoreTest : InstrumentedTest { // Note: we cannot be sure what will be the result of getLastUsedSessionId() here - olmSessionWrapper2.onMessageReceived() + olmSessionWrapper2.onMessageReceived(clock.epochMillis()) cryptoStore.storeSession(olmSessionWrapper2, DUMMY_DEVICE_KEY) // sessionId2 is returned now @@ -114,7 +124,7 @@ class CryptoStoreTest : InstrumentedTest { Thread.sleep(2) - olmSessionWrapper1.onMessageReceived() + olmSessionWrapper1.onMessageReceived(clock.epochMillis()) cryptoStore.storeSession(olmSessionWrapper1, DUMMY_DEVICE_KEY) // sessionId1 is returned now diff --git a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/E2eeSanityTests.kt b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/E2eeSanityTests.kt index 3884683151..38597269cb 100644 --- a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/E2eeSanityTests.kt +++ b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/E2eeSanityTests.kt @@ -23,6 +23,7 @@ import org.amshove.kluent.fail import org.amshove.kluent.internal.assertEquals import org.junit.Assert import org.junit.FixMethodOrder +import org.junit.Rule import org.junit.Test import org.junit.runner.RunWith import org.junit.runners.JUnit4 @@ -30,33 +31,45 @@ import org.junit.runners.MethodSorters import org.matrix.android.sdk.InstrumentedTest import org.matrix.android.sdk.api.session.Session import org.matrix.android.sdk.api.session.crypto.MXCryptoError +import org.matrix.android.sdk.api.session.crypto.RequestResult import org.matrix.android.sdk.api.session.crypto.keysbackup.KeysVersion import org.matrix.android.sdk.api.session.crypto.keysbackup.KeysVersionResult import org.matrix.android.sdk.api.session.crypto.keysbackup.MegolmBackupCreationInfo +import org.matrix.android.sdk.api.session.crypto.model.CryptoDeviceInfo import org.matrix.android.sdk.api.session.crypto.model.ImportRoomKeysResult -import org.matrix.android.sdk.api.session.crypto.model.OlmDecryptionResult +import org.matrix.android.sdk.api.session.crypto.verification.IncomingSasVerificationTransaction +import org.matrix.android.sdk.api.session.crypto.verification.OutgoingSasVerificationTransaction +import org.matrix.android.sdk.api.session.crypto.verification.PendingVerificationRequest +import org.matrix.android.sdk.api.session.crypto.verification.VerificationMethod +import org.matrix.android.sdk.api.session.crypto.verification.VerificationService +import org.matrix.android.sdk.api.session.crypto.verification.VerificationTransaction import org.matrix.android.sdk.api.session.events.model.EventType import org.matrix.android.sdk.api.session.events.model.content.EncryptedEventContent +import org.matrix.android.sdk.api.session.events.model.content.WithHeldCode import org.matrix.android.sdk.api.session.events.model.toModel +import org.matrix.android.sdk.api.session.getRoom +import org.matrix.android.sdk.api.session.getRoomSummary import org.matrix.android.sdk.api.session.room.Room import org.matrix.android.sdk.api.session.room.failure.JoinRoomFailure +import org.matrix.android.sdk.api.session.room.getTimelineEvent import org.matrix.android.sdk.api.session.room.model.Membership import org.matrix.android.sdk.api.session.room.model.message.MessageContent import org.matrix.android.sdk.api.session.room.send.SendState import org.matrix.android.sdk.api.session.room.timeline.TimelineSettings import org.matrix.android.sdk.common.CommonTestHelper import org.matrix.android.sdk.common.CryptoTestHelper +import org.matrix.android.sdk.common.RetryTestRule import org.matrix.android.sdk.common.SessionTestParams import org.matrix.android.sdk.common.TestConstants import org.matrix.android.sdk.common.TestMatrixCallback +import java.util.concurrent.CountDownLatch @RunWith(JUnit4::class) @FixMethodOrder(MethodSorters.JVM) @LargeTest class E2eeSanityTests : InstrumentedTest { - private val testHelper = CommonTestHelper(context()) - private val cryptoTestHelper = CryptoTestHelper(testHelper) + @get:Rule val rule = RetryTestRule(3) /** * Simple test that create an e2ee room. @@ -69,16 +82,24 @@ class E2eeSanityTests : InstrumentedTest { */ @Test fun testSendingE2EEMessages() { + val testHelper = CommonTestHelper(context()) + val cryptoTestHelper = CryptoTestHelper(testHelper) + val cryptoTestData = cryptoTestHelper.doE2ETestWithAliceAndBobInARoom(true) val aliceSession = cryptoTestData.firstSession val e2eRoomID = cryptoTestData.roomId val aliceRoomPOV = aliceSession.getRoom(e2eRoomID)!! + // we want to disable key gossiping to just check initial sending of keys + aliceSession.cryptoService().enableKeyGossiping(false) + cryptoTestData.secondSession?.cryptoService()?.enableKeyGossiping(false) // add some more users and invite them val otherAccounts = listOf("benoit", "valere", "ganfra") // , "adam", "manu") .map { - testHelper.createAccount(it, SessionTestParams(true)) + testHelper.createAccount(it, SessionTestParams(true)).also { + it.cryptoService().enableKeyGossiping(false) + } } Log.v("#E2E TEST", "All accounts created") @@ -86,24 +107,24 @@ class E2eeSanityTests : InstrumentedTest { otherAccounts.forEach { testHelper.runBlockingTest { Log.v("#E2E TEST", "Alice invites ${it.myUserId}") - aliceRoomPOV.invite(it.myUserId) + aliceRoomPOV.membershipService().invite(it.myUserId) } } // All user should accept invite otherAccounts.forEach { otherSession -> - waitForAndAcceptInviteInRoom(otherSession, e2eRoomID) + waitForAndAcceptInviteInRoom(testHelper, otherSession, e2eRoomID) Log.v("#E2E TEST", "${otherSession.myUserId} joined room $e2eRoomID") } // check that alice see them as joined (not really necessary?) - ensureMembersHaveJoined(aliceSession, otherAccounts, e2eRoomID) + ensureMembersHaveJoined(testHelper, aliceSession, otherAccounts, e2eRoomID) Log.v("#E2E TEST", "All users have joined the room") Log.v("#E2E TEST", "Alice is sending the message") val text = "This is my message" - val sentEventId: String? = sendMessageInRoom(aliceRoomPOV, text) + val sentEventId: String? = sendMessageInRoom(testHelper, aliceRoomPOV, text) // val sentEvent = testHelper.sendTextMessage(aliceRoomPOV, "Hello all", 1).first() Assert.assertTrue("Message should be sent", sentEventId != null) @@ -111,10 +132,10 @@ class E2eeSanityTests : InstrumentedTest { otherAccounts.forEach { otherSession -> testHelper.waitWithLatch { latch -> testHelper.retryPeriodicallyWithLatch(latch) { - val timelineEvent = otherSession.getRoom(e2eRoomID)?.getTimelineEvent(sentEventId!!) - timelineEvent != null && - timelineEvent.isEncrypted() && - timelineEvent.root.getClearType() == EventType.MESSAGE + val timeLineEvent = otherSession.getRoom(e2eRoomID)?.getTimelineEvent(sentEventId!!) + timeLineEvent != null && + timeLineEvent.isEncrypted() && + timeLineEvent.root.getClearType() == EventType.MESSAGE } } } @@ -128,15 +149,15 @@ class E2eeSanityTests : InstrumentedTest { newAccount.forEach { testHelper.runBlockingTest { Log.v("#E2E TEST", "Alice invites ${it.myUserId}") - aliceRoomPOV.invite(it.myUserId) + aliceRoomPOV.membershipService().invite(it.myUserId) } } newAccount.forEach { - waitForAndAcceptInviteInRoom(it, e2eRoomID) + waitForAndAcceptInviteInRoom(testHelper, it, e2eRoomID) } - ensureMembersHaveJoined(aliceSession, newAccount, e2eRoomID) + ensureMembersHaveJoined(testHelper, aliceSession, newAccount, e2eRoomID) // wait a bit testHelper.runBlockingTest { @@ -161,7 +182,7 @@ class E2eeSanityTests : InstrumentedTest { Log.v("#E2E TEST", "Alice sends a new message") val secondMessage = "2 This is my message" - val secondSentEventId: String? = sendMessageInRoom(aliceRoomPOV, secondMessage) + val secondSentEventId: String? = sendMessageInRoom(testHelper, aliceRoomPOV, secondMessage) // new members should be able to decrypt it newAccount.forEach { otherSession -> @@ -185,6 +206,14 @@ class E2eeSanityTests : InstrumentedTest { cryptoTestData.cleanUp(testHelper) } + @Test + fun testKeyGossipingIsEnabledByDefault() { + val testHelper = CommonTestHelper(context()) + val session = testHelper.createAccount("alice", SessionTestParams(true)) + Assert.assertTrue("Key gossiping should be enabled by default", session.cryptoService().isKeyGossipingEnabled()) + testHelper.signOutAndClose(session) + } + /** * Quick test for basic key backup * 1. Create e2e between Alice and Bob @@ -201,6 +230,9 @@ class E2eeSanityTests : InstrumentedTest { */ @Test fun testBasicBackupImport() { + val testHelper = CommonTestHelper(context()) + val cryptoTestHelper = CryptoTestHelper(testHelper) + val cryptoTestData = cryptoTestHelper.doE2ETestWithAliceAndBobInARoom(true) val aliceSession = cryptoTestData.firstSession val bobSession = cryptoTestData.secondSession!! @@ -224,16 +256,16 @@ class E2eeSanityTests : InstrumentedTest { val sentEventIds = mutableListOf() val messagesText = listOf("1. Hello", "2. Bob", "3. Good morning") messagesText.forEach { text -> - val sentEventId = sendMessageInRoom(aliceRoomPOV, text)!!.also { + val sentEventId = sendMessageInRoom(testHelper, aliceRoomPOV, text)!!.also { sentEventIds.add(it) } testHelper.waitWithLatch { latch -> testHelper.retryPeriodicallyWithLatch(latch) { - val timelineEvent = bobSession.getRoom(e2eRoomID)?.getTimelineEvent(sentEventId) - timelineEvent != null && - timelineEvent.isEncrypted() && - timelineEvent.root.getClearType() == EventType.MESSAGE + val timeLineEvent = bobSession.getRoom(e2eRoomID)?.getTimelineEvent(sentEventId) + timeLineEvent != null && + timeLineEvent.isEncrypted() && + timeLineEvent.root.getClearType() == EventType.MESSAGE } } // we want more so let's discard the session @@ -286,28 +318,31 @@ class E2eeSanityTests : InstrumentedTest { } } // after initial sync events are not decrypted, so we have to try manually - ensureCannotDecrypt(sentEventIds, newBobSession, e2eRoomID, MXCryptoError.ErrorType.UNKNOWN_INBOUND_SESSION_ID) + cryptoTestHelper.ensureCannotDecrypt(sentEventIds, newBobSession, e2eRoomID, MXCryptoError.ErrorType.UNKNOWN_INBOUND_SESSION_ID) // Let's now import keys from backup - newBobSession.cryptoService().keysBackupService().let { keysBackupService -> + newBobSession.cryptoService().keysBackupService().let { kbs -> val keyVersionResult = testHelper.doSync { - keysBackupService.getVersion(version.version, it) + kbs.getVersion(version.version, it) } val importedResult = testHelper.doSync { - keysBackupService.restoreKeyBackupWithPassword(keyVersionResult!!, + kbs.restoreKeyBackupWithPassword( + keyVersionResult!!, keyBackupPassword, null, null, - null, it) + null, + it + ) } assertEquals(3, importedResult.totalNumberOfKeys) } // ensure bob can now decrypt - ensureCanDecrypt(sentEventIds, newBobSession, e2eRoomID, messagesText) + cryptoTestHelper.ensureCanDecrypt(sentEventIds, newBobSession, e2eRoomID, messagesText) testHelper.signOutAndClose(newBobSession) } @@ -318,6 +353,9 @@ class E2eeSanityTests : InstrumentedTest { */ @Test fun testSimpleGossip() { + val testHelper = CommonTestHelper(context()) + val cryptoTestHelper = CryptoTestHelper(testHelper) + val cryptoTestData = cryptoTestHelper.doE2ETestWithAliceAndBobInARoom(true) val aliceSession = cryptoTestData.firstSession val bobSession = cryptoTestData.secondSession!! @@ -325,30 +363,28 @@ class E2eeSanityTests : InstrumentedTest { val aliceRoomPOV = aliceSession.getRoom(e2eRoomID)!! - cryptoTestHelper.initializeCrossSigning(bobSession) - // let's send a few message to bob val sentEventIds = mutableListOf() val messagesText = listOf("1. Hello", "2. Bob") Log.v("#E2E TEST", "Alice sends some messages") messagesText.forEach { text -> - val sentEventId = sendMessageInRoom(aliceRoomPOV, text)!!.also { + val sentEventId = sendMessageInRoom(testHelper, aliceRoomPOV, text)!!.also { sentEventIds.add(it) } testHelper.waitWithLatch { latch -> testHelper.retryPeriodicallyWithLatch(latch) { - val timelineEvent = bobSession.getRoom(e2eRoomID)?.getTimelineEvent(sentEventId) - timelineEvent != null && - timelineEvent.isEncrypted() && - timelineEvent.root.getClearType() == EventType.MESSAGE + val timeLineEvent = bobSession.getRoom(e2eRoomID)?.getTimelineEvent(sentEventId) + timeLineEvent != null && + timeLineEvent.isEncrypted() && + timeLineEvent.root.getClearType() == EventType.MESSAGE } } } // Ensure bob can decrypt - ensureIsDecrypted(sentEventIds, bobSession, e2eRoomID) + ensureIsDecrypted(testHelper, sentEventIds, bobSession, e2eRoomID) // Let's now add a new bob session // Create a new session for bob @@ -358,7 +394,11 @@ class E2eeSanityTests : InstrumentedTest { // check that new bob can't currently decrypt Log.v("#E2E TEST", "check that new bob can't currently decrypt") - ensureCannotDecrypt(sentEventIds, newBobSession, e2eRoomID, MXCryptoError.ErrorType.UNKNOWN_INBOUND_SESSION_ID) + cryptoTestHelper.ensureCannotDecrypt(sentEventIds, newBobSession, e2eRoomID, null) +// newBobSession.cryptoService().getOutgoingRoomKeyRequests() +// .firstOrNull { +// it.sessionId == +// } // Try to request sentEventIds.forEach { sentEventId -> @@ -367,12 +407,34 @@ class E2eeSanityTests : InstrumentedTest { } // wait a bit - testHelper.runBlockingTest { - delay(10_000) - } + // we need to wait a couple of syncs to let sharing occurs +// testHelper.waitFewSyncs(newBobSession, 6) // Ensure that new bob still can't decrypt (keys must have been withheld) - ensureCannotDecrypt(sentEventIds, newBobSession, e2eRoomID, MXCryptoError.ErrorType.KEYS_WITHHELD) + sentEventIds.forEach { sentEventId -> + val megolmSessionId = newBobSession.getRoom(e2eRoomID)!! + .getTimelineEvent(sentEventId)!! + .root.content.toModel()!!.sessionId + testHelper.waitWithLatch { latch -> + testHelper.retryPeriodicallyWithLatch(latch) { + val aliceReply = newBobSession.cryptoService().getOutgoingRoomKeyRequests() + .first { + it.sessionId == megolmSessionId && + it.roomId == e2eRoomID + } + .results.also { + Log.w("##TEST", "result list is $it") + } + .firstOrNull { it.userId == aliceSession.myUserId } + ?.result + aliceReply != null && + aliceReply is RequestResult.Failure && + WithHeldCode.UNAUTHORISED == aliceReply.code + } + } + } + + cryptoTestHelper.ensureCannotDecrypt(sentEventIds, newBobSession, e2eRoomID, null) // Now mark new bob session as verified @@ -385,12 +447,7 @@ class E2eeSanityTests : InstrumentedTest { newBobSession.cryptoService().reRequestRoomKeyForEvent(event) } - // wait a bit - testHelper.runBlockingTest { - delay(10_000) - } - - ensureCanDecrypt(sentEventIds, newBobSession, e2eRoomID, messagesText) + cryptoTestHelper.ensureCanDecrypt(sentEventIds, newBobSession, e2eRoomID, messagesText) cryptoTestData.cleanUp(testHelper) testHelper.signOutAndClose(newBobSession) @@ -401,6 +458,9 @@ class E2eeSanityTests : InstrumentedTest { */ @Test fun testForwardBetterKey() { + val testHelper = CommonTestHelper(context()) + val cryptoTestHelper = CryptoTestHelper(testHelper) + val cryptoTestData = cryptoTestHelper.doE2ETestWithAliceAndBobInARoom(true) val aliceSession = cryptoTestData.firstSession val bobSessionWithBetterKey = cryptoTestData.secondSession!! @@ -408,35 +468,33 @@ class E2eeSanityTests : InstrumentedTest { val aliceRoomPOV = aliceSession.getRoom(e2eRoomID)!! - cryptoTestHelper.initializeCrossSigning(bobSessionWithBetterKey) - // let's send a few message to bob var firstEventId: String val firstMessage = "1. Hello" Log.v("#E2E TEST", "Alice sends some messages") firstMessage.let { text -> - firstEventId = sendMessageInRoom(aliceRoomPOV, text)!! + firstEventId = sendMessageInRoom(testHelper, aliceRoomPOV, text)!! testHelper.waitWithLatch { latch -> testHelper.retryPeriodicallyWithLatch(latch) { - val timelineEvent = bobSessionWithBetterKey.getRoom(e2eRoomID)?.getTimelineEvent(firstEventId) - timelineEvent != null && - timelineEvent.isEncrypted() && - timelineEvent.root.getClearType() == EventType.MESSAGE + val timeLineEvent = bobSessionWithBetterKey.getRoom(e2eRoomID)?.getTimelineEvent(firstEventId) + timeLineEvent != null && + timeLineEvent.isEncrypted() && + timeLineEvent.root.getClearType() == EventType.MESSAGE } } } // Ensure bob can decrypt - ensureIsDecrypted(listOf(firstEventId), bobSessionWithBetterKey, e2eRoomID) + ensureIsDecrypted(testHelper, listOf(firstEventId), bobSessionWithBetterKey, e2eRoomID) // Let's add a new unverified session from bob val newBobSession = testHelper.logIntoAccount(bobSessionWithBetterKey.myUserId, SessionTestParams(true)) // check that new bob can't currently decrypt Log.v("#E2E TEST", "check that new bob can't currently decrypt") - ensureCannotDecrypt(listOf(firstEventId), newBobSession, e2eRoomID, MXCryptoError.ErrorType.UNKNOWN_INBOUND_SESSION_ID) + cryptoTestHelper.ensureCannotDecrypt(listOf(firstEventId), newBobSession, e2eRoomID, null) // Now let alice send a new message. this time the new bob session will be able to decrypt var secondEventId: String @@ -444,14 +502,14 @@ class E2eeSanityTests : InstrumentedTest { Log.v("#E2E TEST", "Alice sends some messages") secondMessage.let { text -> - secondEventId = sendMessageInRoom(aliceRoomPOV, text)!! + secondEventId = sendMessageInRoom(testHelper, aliceRoomPOV, text)!! testHelper.waitWithLatch { latch -> testHelper.retryPeriodicallyWithLatch(latch) { - val timelineEvent = newBobSession.getRoom(e2eRoomID)?.getTimelineEvent(secondEventId) - timelineEvent != null && - timelineEvent.isEncrypted() && - timelineEvent.root.getClearType() == EventType.MESSAGE + val timeLineEvent = newBobSession.getRoom(e2eRoomID)?.getTimelineEvent(secondEventId) + timeLineEvent != null && + timeLineEvent.isEncrypted() && + timeLineEvent.root.getClearType() == EventType.MESSAGE } } } @@ -470,9 +528,7 @@ class E2eeSanityTests : InstrumentedTest { try { newBobSession.cryptoService().decryptEvent(firstEventNewBobPov.root, "") fail("Should not be able to decrypt event") - } catch (error: MXCryptoError) { - val errorType = (error as? MXCryptoError.Base)?.errorType - assertEquals(MXCryptoError.ErrorType.UNKNOWN_MESSAGE_INDEX, errorType) + } catch (_: MXCryptoError) { } } @@ -494,41 +550,45 @@ class E2eeSanityTests : InstrumentedTest { .markedLocallyAsManuallyVerified(bobSessionWithBetterKey.myUserId, bobSessionWithBetterKey.sessionParams.deviceId!!) // now let new session request - newBobSession.cryptoService().requestRoomKeyForEvent(firstEventNewBobPov.root) + newBobSession.cryptoService().reRequestRoomKeyForEvent(firstEventNewBobPov.root) - // wait a bit - testHelper.runBlockingTest { - delay(10_000) - } + // We need to wait for the key request to be sent out and then a reply to be received // old session should have shared the key at earliest known index now // we should be able to decrypt both - testHelper.runBlockingTest { - try { - newBobSession.cryptoService().decryptEvent(firstEventNewBobPov.root, "") - } catch (error: MXCryptoError) { - fail("Should be able to decrypt first event now $error") - } - } - testHelper.runBlockingTest { - try { - newBobSession.cryptoService().decryptEvent(secondEventNewBobPov.root, "") - } catch (error: MXCryptoError) { - fail("Should be able to decrypt event $error") + testHelper.waitWithLatch { + testHelper.retryPeriodicallyWithLatch(it) { + val canDecryptFirst = try { + testHelper.runBlockingTest { + newBobSession.cryptoService().decryptEvent(firstEventNewBobPov.root, "") + } + true + } catch (error: MXCryptoError) { + false + } + val canDecryptSecond = try { + testHelper.runBlockingTest { + newBobSession.cryptoService().decryptEvent(secondEventNewBobPov.root, "") + } + true + } catch (error: MXCryptoError) { + false + } + canDecryptFirst && canDecryptSecond } } - cryptoTestData.cleanUp(testHelper) + testHelper.signOutAndClose(aliceSession) + testHelper.signOutAndClose(bobSessionWithBetterKey) testHelper.signOutAndClose(newBobSession) } - private fun sendMessageInRoom(aliceRoomPOV: Room, text: String): String? { - aliceRoomPOV.sendTextMessage(text) + private fun sendMessageInRoom(testHelper: CommonTestHelper, aliceRoomPOV: Room, text: String): String? { + aliceRoomPOV.sendService().sendTextMessage(text) var sentEventId: String? = null testHelper.waitWithLatch(4 * TestConstants.timeOutMillis) { latch -> - val timeline = aliceRoomPOV.createTimeline(null, TimelineSettings(60)) + val timeline = aliceRoomPOV.timelineService().createTimeline(null, TimelineSettings(60)) timeline.start() - testHelper.retryPeriodicallyWithLatch(latch) { val decryptedMsg = timeline.getSnapshot() .filter { it.root.getClearType() == EventType.MESSAGE } @@ -547,11 +607,161 @@ class E2eeSanityTests : InstrumentedTest { return sentEventId } - private fun ensureMembersHaveJoined(aliceSession: Session, otherAccounts: List, e2eRoomID: String) { + /** + * Test that if a better key is forwared (lower index, it is then used) + */ + @Test + fun testSelfInteractiveVerificationAndGossip() { + val testHelper = CommonTestHelper(context()) + val cryptoTestHelper = CryptoTestHelper(testHelper) + + val aliceSession = testHelper.createAccount("alice", SessionTestParams(true)) + cryptoTestHelper.bootstrapSecurity(aliceSession) + + // now let's create a new login from alice + + val aliceNewSession = testHelper.logIntoAccount(aliceSession.myUserId, SessionTestParams(true)) + + val oldCompleteLatch = CountDownLatch(1) + lateinit var oldCode: String + aliceSession.cryptoService().verificationService().addListener(object : VerificationService.Listener { + + override fun verificationRequestUpdated(pr: PendingVerificationRequest) { + val readyInfo = pr.readyInfo + if (readyInfo != null) { + aliceSession.cryptoService().verificationService().beginKeyVerification( + VerificationMethod.SAS, + aliceSession.myUserId, + readyInfo.fromDevice, + readyInfo.transactionId + + ) + } + } + + override fun transactionUpdated(tx: VerificationTransaction) { + Log.d("##TEST", "exitsingPov: $tx") + val sasTx = tx as OutgoingSasVerificationTransaction + when (sasTx.uxState) { + OutgoingSasVerificationTransaction.UxState.SHOW_SAS -> { + // for the test we just accept? + oldCode = sasTx.getDecimalCodeRepresentation() + sasTx.userHasVerifiedShortCode() + } + OutgoingSasVerificationTransaction.UxState.VERIFIED -> { + // we can release this latch? + oldCompleteLatch.countDown() + } + else -> Unit + } + } + }) + + val newCompleteLatch = CountDownLatch(1) + lateinit var newCode: String + aliceNewSession.cryptoService().verificationService().addListener(object : VerificationService.Listener { + + override fun verificationRequestCreated(pr: PendingVerificationRequest) { + // let's ready + aliceNewSession.cryptoService().verificationService().readyPendingVerification( + listOf(VerificationMethod.SAS, VerificationMethod.QR_CODE_SCAN, VerificationMethod.QR_CODE_SHOW), + aliceSession.myUserId, + pr.transactionId!! + ) + } + + var matchOnce = true + override fun transactionUpdated(tx: VerificationTransaction) { + Log.d("##TEST", "newPov: $tx") + + val sasTx = tx as IncomingSasVerificationTransaction + when (sasTx.uxState) { + IncomingSasVerificationTransaction.UxState.SHOW_ACCEPT -> { + // no need to accept as there was a request first it will auto accept + } + IncomingSasVerificationTransaction.UxState.SHOW_SAS -> { + if (matchOnce) { + sasTx.userHasVerifiedShortCode() + newCode = sasTx.getDecimalCodeRepresentation() + matchOnce = false + } + } + IncomingSasVerificationTransaction.UxState.VERIFIED -> { + newCompleteLatch.countDown() + } + else -> Unit + } + } + }) + + // initiate self verification + aliceSession.cryptoService().verificationService().requestKeyVerification( + listOf(VerificationMethod.SAS, VerificationMethod.QR_CODE_SCAN, VerificationMethod.QR_CODE_SHOW), + aliceNewSession.myUserId, + listOf(aliceNewSession.sessionParams.deviceId!!) + ) + testHelper.await(oldCompleteLatch) + testHelper.await(newCompleteLatch) + assertEquals("Decimal code should have matched", oldCode, newCode) + + // Assert that devices are verified + val newDeviceFromOldPov: CryptoDeviceInfo? = aliceSession.cryptoService().getDeviceInfo(aliceSession.myUserId, aliceNewSession.sessionParams.deviceId) + val oldDeviceFromNewPov: CryptoDeviceInfo? = aliceSession.cryptoService().getDeviceInfo(aliceSession.myUserId, aliceSession.sessionParams.deviceId) + + Assert.assertTrue("new device should be verified from old point of view", newDeviceFromOldPov!!.isVerified) + Assert.assertTrue("old device should be verified from new point of view", oldDeviceFromNewPov!!.isVerified) + + // wait for secret gossiping to happen + testHelper.waitWithLatch { latch -> + testHelper.retryPeriodicallyWithLatch(latch) { + aliceNewSession.cryptoService().crossSigningService().allPrivateKeysKnown() + } + } + + testHelper.waitWithLatch { latch -> + testHelper.retryPeriodicallyWithLatch(latch) { + aliceNewSession.cryptoService().keysBackupService().getKeyBackupRecoveryKeyInfo() != null + } + } + + assertEquals( + "MSK Private parts should be the same", + aliceSession.cryptoService().crossSigningService().getCrossSigningPrivateKeys()!!.master, + aliceNewSession.cryptoService().crossSigningService().getCrossSigningPrivateKeys()!!.master + ) + assertEquals( + "USK Private parts should be the same", + aliceSession.cryptoService().crossSigningService().getCrossSigningPrivateKeys()!!.user, + aliceNewSession.cryptoService().crossSigningService().getCrossSigningPrivateKeys()!!.user + ) + + assertEquals( + "SSK Private parts should be the same", + aliceSession.cryptoService().crossSigningService().getCrossSigningPrivateKeys()!!.selfSigned, + aliceNewSession.cryptoService().crossSigningService().getCrossSigningPrivateKeys()!!.selfSigned + ) + + // Let's check that we have the megolm backup key + assertEquals( + "Megolm key should be the same", + aliceSession.cryptoService().keysBackupService().getKeyBackupRecoveryKeyInfo()!!.recoveryKey, + aliceNewSession.cryptoService().keysBackupService().getKeyBackupRecoveryKeyInfo()!!.recoveryKey + ) + assertEquals( + "Megolm version should be the same", + aliceSession.cryptoService().keysBackupService().getKeyBackupRecoveryKeyInfo()!!.version, + aliceNewSession.cryptoService().keysBackupService().getKeyBackupRecoveryKeyInfo()!!.version + ) + + testHelper.signOutAndClose(aliceSession) + testHelper.signOutAndClose(aliceNewSession) + } + + private fun ensureMembersHaveJoined(testHelper: CommonTestHelper, aliceSession: Session, otherAccounts: List, e2eRoomID: String) { testHelper.waitWithLatch { latch -> testHelper.retryPeriodicallyWithLatch(latch) { otherAccounts.map { - aliceSession.getRoomMember(it.myUserId, e2eRoomID)?.membership + aliceSession.roomService().getRoomMember(it.myUserId, e2eRoomID)?.membership }.all { it == Membership.JOIN } @@ -559,7 +769,7 @@ class E2eeSanityTests : InstrumentedTest { } } - private fun waitForAndAcceptInviteInRoom(otherSession: Session, e2eRoomID: String) { + private fun waitForAndAcceptInviteInRoom(testHelper: CommonTestHelper, otherSession: Session, e2eRoomID: String) { testHelper.waitWithLatch { latch -> testHelper.retryPeriodicallyWithLatch(latch) { val roomSummary = otherSession.getRoomSummary(e2eRoomID) @@ -571,10 +781,11 @@ class E2eeSanityTests : InstrumentedTest { } } - testHelper.runBlockingTest(60_000) { + // not sure why it's taking so long :/ + testHelper.runBlockingTest(90_000) { Log.v("#E2E TEST", "${otherSession.myUserId} tries to join room $e2eRoomID") try { - otherSession.joinRoom(e2eRoomID) + otherSession.roomService().joinRoom(e2eRoomID) } catch (ex: JoinRoomFailure.JoinedWithTimeout) { // it's ok we will wait after } @@ -589,59 +800,14 @@ class E2eeSanityTests : InstrumentedTest { } } - private fun ensureCanDecrypt(sentEventIds: MutableList, session: Session, e2eRoomID: String, messagesText: List) { - sentEventIds.forEachIndexed { index, sentEventId -> - testHelper.waitWithLatch { latch -> - testHelper.retryPeriodicallyWithLatch(latch) { - val event = session.getRoom(e2eRoomID)!!.getTimelineEvent(sentEventId)!!.root - testHelper.runBlockingTest { - try { - session.cryptoService().decryptEvent(event, "").let { result -> - event.mxDecryptionResult = OlmDecryptionResult( - payload = result.clearEvent, - senderKey = result.senderCurve25519Key, - keysClaimed = result.claimedEd25519Key?.let { mapOf("ed25519" to it) }, - forwardingCurve25519KeyChain = result.forwardingCurve25519KeyChain - ) - } - } catch (error: MXCryptoError) { - // nop - } - } - event.getClearType() == EventType.MESSAGE && - messagesText[index] == event.getClearContent()?.toModel()?.body - } - } - } - } - - private fun ensureIsDecrypted(sentEventIds: List, session: Session, e2eRoomID: String) { + private fun ensureIsDecrypted(testHelper: CommonTestHelper, sentEventIds: List, session: Session, e2eRoomID: String) { testHelper.waitWithLatch { latch -> sentEventIds.forEach { sentEventId -> testHelper.retryPeriodicallyWithLatch(latch) { - val timelineEvent = session.getRoom(e2eRoomID)?.getTimelineEvent(sentEventId) - timelineEvent != null && - timelineEvent.isEncrypted() && - timelineEvent.root.getClearType() == EventType.MESSAGE - } - } - } - } - - private fun ensureCannotDecrypt(sentEventIds: List, newBobSession: Session, e2eRoomID: String, expectedError: MXCryptoError.ErrorType?) { - sentEventIds.forEach { sentEventId -> - val event = newBobSession.getRoom(e2eRoomID)!!.getTimelineEvent(sentEventId)!!.root - testHelper.runBlockingTest { - try { - newBobSession.cryptoService().decryptEvent(event, "") - fail("Should not be able to decrypt event") - } catch (error: MXCryptoError) { - val errorType = (error as? MXCryptoError.Base)?.errorType - if (expectedError == null) { - Assert.assertNotNull(errorType) - } else { - assertEquals(expectedError, errorType, "Message expected to be UISI") - } + val timeLineEvent = session.getRoom(e2eRoomID)?.getTimelineEvent(sentEventId) + timeLineEvent != null && + timeLineEvent.isEncrypted() && + timeLineEvent.root.getClearType() == EventType.MESSAGE } } } diff --git a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/ExportEncryptionTest.kt b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/ExportEncryptionTest.kt index 17664c78aa..c2d8f4fb35 100644 --- a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/ExportEncryptionTest.kt +++ b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/ExportEncryptionTest.kt @@ -93,9 +93,11 @@ class ExportEncryptionTest { fail("## checkExportDecrypt1() failed : " + e.message) } - assertEquals("## checkExportDecrypt1() : expectedString $expectedString -- decodedString $decodedString", + assertEquals( + "## checkExportDecrypt1() : expectedString $expectedString -- decodedString $decodedString", expectedString, - decodedString) + decodedString + ) } @Test @@ -111,9 +113,11 @@ class ExportEncryptionTest { fail("## checkExportDecrypt2() failed : " + e.message) } - assertEquals("## checkExportDecrypt2() : expectedString $expectedString -- decodedString $decodedString", + assertEquals( + "## checkExportDecrypt2() : expectedString $expectedString -- decodedString $decodedString", expectedString, - decodedString) + decodedString + ) } @Test @@ -129,9 +133,11 @@ class ExportEncryptionTest { fail("## checkExportDecrypt3() failed : " + e.message) } - assertEquals("## checkExportDecrypt3() : expectedString $expectedString -- decodedString $decodedString", + assertEquals( + "## checkExportDecrypt3() : expectedString $expectedString -- decodedString $decodedString", expectedString, - decodedString) + decodedString + ) } @Test @@ -147,9 +153,11 @@ class ExportEncryptionTest { fail("## checkExportEncrypt1() failed : " + e.message) } - assertEquals("## checkExportEncrypt1() : expectedString $expectedString -- decodedString $decodedString", + assertEquals( + "## checkExportEncrypt1() : expectedString $expectedString -- decodedString $decodedString", expectedString, - decodedString) + decodedString + ) } @Test @@ -165,9 +173,11 @@ class ExportEncryptionTest { fail("## checkExportEncrypt2() failed : " + e.message) } - assertEquals("## checkExportEncrypt2() : expectedString $expectedString -- decodedString $decodedString", + assertEquals( + "## checkExportEncrypt2() : expectedString $expectedString -- decodedString $decodedString", expectedString, - decodedString) + decodedString + ) } @Test @@ -183,9 +193,11 @@ class ExportEncryptionTest { fail("## checkExportEncrypt3() failed : " + e.message) } - assertEquals("## checkExportEncrypt3() : expectedString $expectedString -- decodedString $decodedString", + assertEquals( + "## checkExportEncrypt3() : expectedString $expectedString -- decodedString $decodedString", expectedString, - decodedString) + decodedString + ) } @Test @@ -201,8 +213,10 @@ class ExportEncryptionTest { fail("## checkExportEncrypt4() failed : " + e.message) } - assertEquals("## checkExportEncrypt4() : expectedString $expectedString -- decodedString $decodedString", + assertEquals( + "## checkExportEncrypt4() : expectedString $expectedString -- decodedString $decodedString", expectedString, - decodedString) + decodedString + ) } } diff --git a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/PreShareKeysTest.kt b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/PreShareKeysTest.kt index aa9f09314f..93aa78a305 100644 --- a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/PreShareKeysTest.kt +++ b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/PreShareKeysTest.kt @@ -27,8 +27,9 @@ import org.junit.runners.MethodSorters import org.matrix.android.sdk.InstrumentedTest import org.matrix.android.sdk.api.session.events.model.EventType import org.matrix.android.sdk.api.session.events.model.content.EncryptedEventContent -import org.matrix.android.sdk.api.session.events.model.content.RoomKeyContent import org.matrix.android.sdk.api.session.events.model.toModel +import org.matrix.android.sdk.api.session.getRoom +import org.matrix.android.sdk.api.session.room.getTimelineEvent import org.matrix.android.sdk.common.CommonTestHelper import org.matrix.android.sdk.common.CryptoTestHelper @@ -49,10 +50,7 @@ class PreShareKeysTest : InstrumentedTest { // clear any outbound session aliceSession.cryptoService().discardOutboundSession(e2eRoomID) - val preShareCount = bobSession.cryptoService().getGossipingEvents().count { - it.senderId == aliceSession.myUserId && - it.getClearType() == EventType.ROOM_KEY - } + val preShareCount = bobSession.cryptoService().keysBackupService().getTotalNumbersOfKeys() assertEquals("Bob should not have receive any key from alice at this point", 0, preShareCount) Log.d("#Test", "Room Key Received from alice $preShareCount") @@ -64,23 +62,23 @@ class PreShareKeysTest : InstrumentedTest { testHelper.waitWithLatch { latch -> testHelper.retryPeriodicallyWithLatch(latch) { - val newGossipCount = bobSession.cryptoService().getGossipingEvents().count { - it.senderId == aliceSession.myUserId && - it.getClearType() == EventType.ROOM_KEY - } - newGossipCount > preShareCount + val newKeysCount = bobSession.cryptoService().keysBackupService().getTotalNumbersOfKeys() + newKeysCount > preShareCount } } - val latest = bobSession.cryptoService().getGossipingEvents().lastOrNull { - it.senderId == aliceSession.myUserId && - it.getClearType() == EventType.ROOM_KEY - } + val aliceCryptoStore = (aliceSession.cryptoService() as DefaultCryptoService).cryptoStoreForTesting + val aliceOutboundSessionInRoom = aliceCryptoStore.getCurrentOutboundGroupSessionForRoom(e2eRoomID)!!.outboundGroupSession.sessionIdentifier() - val content = latest?.getClearContent().toModel() - assertNotNull("Bob should have received and decrypted a room key event from alice", content) - assertEquals("Wrong room", e2eRoomID, content!!.roomId) - val megolmSessionId = content.sessionId!! + val bobCryptoStore = (bobSession.cryptoService() as DefaultCryptoService).cryptoStoreForTesting + val aliceDeviceBobPov = bobCryptoStore.getUserDevice(aliceSession.myUserId, aliceSession.sessionParams.deviceId!!)!! + val bobInboundForAlice = bobCryptoStore.getInboundGroupSession(aliceOutboundSessionInRoom, aliceDeviceBobPov.identityKey()!!) + assertNotNull("Bob should have received and decrypted a room key event from alice", bobInboundForAlice) + assertEquals("Wrong room", e2eRoomID, bobInboundForAlice!!.roomId) + + val megolmSessionId = bobInboundForAlice.olmInboundGroupSession!!.sessionIdentifier() + + assertEquals("Wrong session", aliceOutboundSessionInRoom, megolmSessionId) val sharedIndex = aliceSession.cryptoService().getSharedWithInfo(e2eRoomID, megolmSessionId) .getObject(bobSession.myUserId, bobSession.sessionParams.deviceId) diff --git a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/UnwedgingTest.kt b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/UnwedgingTest.kt index 8346430530..0f3a4b4181 100644 --- a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/UnwedgingTest.kt +++ b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/UnwedgingTest.kt @@ -34,6 +34,7 @@ import org.matrix.android.sdk.api.session.crypto.MXCryptoError import org.matrix.android.sdk.api.session.events.model.EventType import org.matrix.android.sdk.api.session.events.model.content.EncryptedEventContent import org.matrix.android.sdk.api.session.events.model.toModel +import org.matrix.android.sdk.api.session.getRoom import org.matrix.android.sdk.api.session.room.timeline.Timeline import org.matrix.android.sdk.api.session.room.timeline.TimelineEvent import org.matrix.android.sdk.api.session.room.timeline.TimelineSettings @@ -97,7 +98,7 @@ class UnwedgingTest : InstrumentedTest { val roomFromBobPOV = bobSession.getRoom(aliceRoomId)!! val roomFromAlicePOV = aliceSession.getRoom(aliceRoomId)!! - val bobTimeline = roomFromBobPOV.createTimeline(null, TimelineSettings(20)) + val bobTimeline = roomFromBobPOV.timelineService().createTimeline(null, TimelineSettings(20)) bobTimeline.start() val bobFinalLatch = CountDownLatch(1) @@ -128,7 +129,7 @@ class UnwedgingTest : InstrumentedTest { messagesReceivedByBob = emptyList() // - Alice sends a 1st message with a 1st megolm session - roomFromAlicePOV.sendTextMessage("First message") + roomFromAlicePOV.sendService().sendTextMessage("First message") // Wait for the message to be received by Bob testHelper.await(latch) @@ -156,7 +157,7 @@ class UnwedgingTest : InstrumentedTest { Timber.i("## CRYPTO | testUnwedging: Alice sends a 2nd message with a 2nd megolm session") // - Alice sends a 2nd message with a 2nd megolm session - roomFromAlicePOV.sendTextMessage("Second message") + roomFromAlicePOV.sendService().sendTextMessage("Second message") // Wait for the message to be received by Bob testHelper.await(latch) @@ -170,7 +171,10 @@ class UnwedgingTest : InstrumentedTest { // Let us wedge the session now. Set crypto state like after the first message Timber.i("## CRYPTO | testUnwedging: wedge the session now. Set crypto state like after the first message") - aliceCryptoStore.storeSession(OlmSessionWrapper(deserializeFromRealm(oldSession)!!), bobSession.cryptoService().getMyDevice().identityKey()!!) + aliceCryptoStore.storeSession( + OlmSessionWrapper(deserializeFromRealm(oldSession)!!), + bobSession.cryptoService().getMyDevice().identityKey()!! + ) olmDevice.clearOlmSessionCache() Thread.sleep(6_000) @@ -185,7 +189,7 @@ class UnwedgingTest : InstrumentedTest { Timber.i("## CRYPTO | testUnwedging: Alice sends a 3rd message with a 3rd megolm session but a wedged olm session") // - Alice sends a 3rd message with a 3rd megolm session but a wedged olm session - roomFromAlicePOV.sendTextMessage("Third message") + roomFromAlicePOV.sendService().sendTextMessage("Third message") // Bob should not be able to decrypt, because the session key could not be sent } bobTimeline.removeListener(bobEventsListener) @@ -217,7 +221,8 @@ class UnwedgingTest : InstrumentedTest { ) ) } - }, it) + }, it + ) } // Wait until we received back the key diff --git a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/crosssigning/XSigningTest.kt b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/crosssigning/XSigningTest.kt index dc65cec187..abcf1714b8 100644 --- a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/crosssigning/XSigningTest.kt +++ b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/crosssigning/XSigningTest.kt @@ -25,7 +25,6 @@ import org.junit.Assert.assertNull import org.junit.Assert.assertTrue import org.junit.Assert.fail import org.junit.FixMethodOrder -import org.junit.Ignore import org.junit.Test import org.junit.runner.RunWith import org.junit.runners.MethodSorters @@ -110,11 +109,13 @@ class XSigningTest : InstrumentedTest { } }, it) } - testHelper.doSync { bobSession.cryptoService().crossSigningService().initializeCrossSigning(object : UserInteractiveAuthInterceptor { - override fun performStage(flowResponse: RegistrationFlowResponse, errCode: String?, promise: Continuation) { - promise.resume(bobAuthParams) - } - }, it) } + testHelper.doSync { + bobSession.cryptoService().crossSigningService().initializeCrossSigning(object : UserInteractiveAuthInterceptor { + override fun performStage(flowResponse: RegistrationFlowResponse, errCode: String?, promise: Continuation) { + promise.resume(bobAuthParams) + } + }, it) + } // Check that alice can see bob keys testHelper.doSync> { aliceSession.cryptoService().downloadKeys(listOf(bobSession.myUserId), true, it) } @@ -124,8 +125,16 @@ class XSigningTest : InstrumentedTest { assertNull("Alice should not see bob User key", bobKeysFromAlicePOV.userKey()) assertNotNull("Alice can see bob SelfSigned key", bobKeysFromAlicePOV.selfSigningKey()) - assertEquals("Bob keys from alice pov should match", bobKeysFromAlicePOV.masterKey()?.unpaddedBase64PublicKey, bobSession.cryptoService().crossSigningService().getMyCrossSigningKeys()?.masterKey()?.unpaddedBase64PublicKey) - assertEquals("Bob keys from alice pov should match", bobKeysFromAlicePOV.selfSigningKey()?.unpaddedBase64PublicKey, bobSession.cryptoService().crossSigningService().getMyCrossSigningKeys()?.selfSigningKey()?.unpaddedBase64PublicKey) + assertEquals( + "Bob keys from alice pov should match", + bobKeysFromAlicePOV.masterKey()?.unpaddedBase64PublicKey, + bobSession.cryptoService().crossSigningService().getMyCrossSigningKeys()?.masterKey()?.unpaddedBase64PublicKey + ) + assertEquals( + "Bob keys from alice pov should match", + bobKeysFromAlicePOV.selfSigningKey()?.unpaddedBase64PublicKey, + bobSession.cryptoService().crossSigningService().getMyCrossSigningKeys()?.selfSigningKey()?.unpaddedBase64PublicKey + ) assertFalse("Bob keys from alice pov should not be trusted", bobKeysFromAlicePOV.isTrusted()) @@ -133,7 +142,6 @@ class XSigningTest : InstrumentedTest { } @Test - @Ignore("This test will be ignored until it is fixed") fun test_CrossSigningTestAliceTrustBobNewDevice() { val cryptoTestData = cryptoTestHelper.doE2ETestWithAliceAndBobInARoom() @@ -149,16 +157,20 @@ class XSigningTest : InstrumentedTest { password = TestConstants.PASSWORD ) - testHelper.doSync { aliceSession.cryptoService().crossSigningService().initializeCrossSigning(object : UserInteractiveAuthInterceptor { - override fun performStage(flowResponse: RegistrationFlowResponse, errCode: String?, promise: Continuation) { - promise.resume(aliceAuthParams) - } - }, it) } - testHelper.doSync { bobSession.cryptoService().crossSigningService().initializeCrossSigning(object : UserInteractiveAuthInterceptor { - override fun performStage(flowResponse: RegistrationFlowResponse, errCode: String?, promise: Continuation) { - promise.resume(bobAuthParams) - } - }, it) } + testHelper.doSync { + aliceSession.cryptoService().crossSigningService().initializeCrossSigning(object : UserInteractiveAuthInterceptor { + override fun performStage(flowResponse: RegistrationFlowResponse, errCode: String?, promise: Continuation) { + promise.resume(aliceAuthParams) + } + }, it) + } + testHelper.doSync { + bobSession.cryptoService().crossSigningService().initializeCrossSigning(object : UserInteractiveAuthInterceptor { + override fun performStage(flowResponse: RegistrationFlowResponse, errCode: String?, promise: Continuation) { + promise.resume(bobAuthParams) + } + }, it) + } // Check that alice can see bob keys val bobUserId = bobSession.myUserId diff --git a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/encryption/EncryptionTest.kt b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/encryption/EncryptionTest.kt index 84c9487e03..85b6c21df3 100644 --- a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/encryption/EncryptionTest.kt +++ b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/encryption/EncryptionTest.kt @@ -28,6 +28,7 @@ import org.matrix.android.sdk.api.crypto.MXCRYPTO_ALGORITHM_MEGOLM import org.matrix.android.sdk.api.session.events.model.EventType import org.matrix.android.sdk.api.session.events.model.content.EncryptionEventContent import org.matrix.android.sdk.api.session.events.model.toContent +import org.matrix.android.sdk.api.session.getRoom import org.matrix.android.sdk.api.session.room.Room import org.matrix.android.sdk.api.session.room.send.SendState import org.matrix.android.sdk.api.session.room.timeline.Timeline @@ -48,7 +49,7 @@ class EncryptionTest : InstrumentedTest { fun test_EncryptionEvent() { performTest(roomShouldBeEncrypted = false) { room -> // Send an encryption Event as an Event (and not as a state event) - room.sendEvent( + room.sendService().sendEvent( eventType = EventType.STATE_ROOM_ENCRYPTION, content = EncryptionEventContent(algorithm = MXCRYPTO_ALGORITHM_MEGOLM).toContent() ) @@ -60,7 +61,7 @@ class EncryptionTest : InstrumentedTest { performTest(roomShouldBeEncrypted = true) { room -> runBlocking { // Send an encryption Event as a State Event - room.sendStateEvent( + room.stateService().sendStateEvent( eventType = EventType.STATE_ROOM_ENCRYPTION, stateKey = "", body = EncryptionEventContent(algorithm = MXCRYPTO_ALGORITHM_MEGOLM).toContent() @@ -75,9 +76,9 @@ class EncryptionTest : InstrumentedTest { val aliceSession = cryptoTestData.firstSession val room = aliceSession.getRoom(cryptoTestData.roomId)!! - room.isEncrypted() shouldBe false + room.roomCryptoService().isEncrypted() shouldBe false - val timeline = room.createTimeline(null, TimelineSettings(10)) + val timeline = room.timelineService().createTimeline(null, TimelineSettings(10)) val latch = CountDownLatch(1) val timelineListener = object : Timeline.Listener { override fun onTimelineFailure(throwable: Throwable) { @@ -105,7 +106,7 @@ class EncryptionTest : InstrumentedTest { testHelper.await(latch) timeline.dispose() testHelper.waitWithLatch { - room.isEncrypted() shouldBe roomShouldBeEncrypted + room.roomCryptoService().isEncrypted() shouldBe roomShouldBeEncrypted it.countDown() } cryptoTestData.cleanUp(testHelper) diff --git a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/gossiping/KeyShareTests.kt b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/gossiping/KeyShareTests.kt index 5e271c6910..1bd2a46381 100644 --- a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/gossiping/KeyShareTests.kt +++ b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/gossiping/KeyShareTests.kt @@ -19,61 +19,53 @@ package org.matrix.android.sdk.internal.crypto.gossiping import android.util.Log import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.LargeTest -import junit.framework.TestCase.assertEquals import junit.framework.TestCase.assertNotNull import junit.framework.TestCase.assertTrue import junit.framework.TestCase.fail +import org.amshove.kluent.internal.assertEquals import org.junit.Assert +import org.junit.Assert.assertNull import org.junit.FixMethodOrder -import org.junit.Ignore +import org.junit.Rule import org.junit.Test import org.junit.runner.RunWith import org.junit.runners.MethodSorters import org.matrix.android.sdk.InstrumentedTest -import org.matrix.android.sdk.api.auth.UIABaseAuth -import org.matrix.android.sdk.api.auth.UserInteractiveAuthInterceptor -import org.matrix.android.sdk.api.auth.UserPasswordAuth -import org.matrix.android.sdk.api.auth.registration.RegistrationFlowResponse -import org.matrix.android.sdk.api.extensions.tryOrNull +import org.matrix.android.sdk.api.session.crypto.OutgoingRoomKeyRequestState +import org.matrix.android.sdk.api.session.crypto.RequestResult import org.matrix.android.sdk.api.session.crypto.crosssigning.DeviceTrustLevel -import org.matrix.android.sdk.api.session.crypto.keysbackup.KeysVersion -import org.matrix.android.sdk.api.session.crypto.keysbackup.MegolmBackupCreationInfo -import org.matrix.android.sdk.api.session.crypto.model.CryptoDeviceInfo -import org.matrix.android.sdk.api.session.crypto.model.GossipingRequestState -import org.matrix.android.sdk.api.session.crypto.model.MXUsersDevicesMap -import org.matrix.android.sdk.api.session.crypto.model.OutgoingGossipingRequestState -import org.matrix.android.sdk.api.session.crypto.verification.IncomingSasVerificationTransaction -import org.matrix.android.sdk.api.session.crypto.verification.SasVerificationTransaction -import org.matrix.android.sdk.api.session.crypto.verification.VerificationMethod -import org.matrix.android.sdk.api.session.crypto.verification.VerificationService -import org.matrix.android.sdk.api.session.crypto.verification.VerificationTransaction -import org.matrix.android.sdk.api.session.crypto.verification.VerificationTxState import org.matrix.android.sdk.api.session.events.model.content.EncryptedEventContent +import org.matrix.android.sdk.api.session.events.model.content.WithHeldCode import org.matrix.android.sdk.api.session.events.model.toModel +import org.matrix.android.sdk.api.session.getRoom +import org.matrix.android.sdk.api.session.room.getTimelineEvent import org.matrix.android.sdk.api.session.room.model.RoomDirectoryVisibility import org.matrix.android.sdk.api.session.room.model.create.CreateRoomParams -import org.matrix.android.sdk.api.session.room.model.message.MessageContent +import org.matrix.android.sdk.api.session.room.timeline.getLastMessageContent import org.matrix.android.sdk.common.CommonTestHelper +import org.matrix.android.sdk.common.CryptoTestHelper +import org.matrix.android.sdk.common.RetryTestRule import org.matrix.android.sdk.common.SessionTestParams import org.matrix.android.sdk.common.TestConstants -import kotlin.coroutines.Continuation -import kotlin.coroutines.resume @RunWith(AndroidJUnit4::class) @FixMethodOrder(MethodSorters.JVM) @LargeTest class KeyShareTests : InstrumentedTest { - private val commonTestHelper = CommonTestHelper(context()) + @get:Rule val rule = RetryTestRule(3) @Test - @Ignore("This test will be ignored until it is fixed") fun test_DoNotSelfShareIfNotTrusted() { + val commonTestHelper = CommonTestHelper(context()) + val cryptoTestHelper = CryptoTestHelper(commonTestHelper) + val aliceSession = commonTestHelper.createAccount(TestConstants.USER_ALICE, SessionTestParams(true)) + Log.v("TEST", "=======> AliceSession 1 is ${aliceSession.sessionParams.deviceId}") // Create an encrypted room and add a message val roomId = commonTestHelper.runBlockingTest { - aliceSession.createRoom( + aliceSession.roomService().createRoom( CreateRoomParams().apply { visibility = RoomDirectoryVisibility.PRIVATE enableEncryption() @@ -83,12 +75,19 @@ class KeyShareTests : InstrumentedTest { val room = aliceSession.getRoom(roomId) assertNotNull(room) Thread.sleep(4_000) - assertTrue(room?.isEncrypted() == true) - val sentEventId = commonTestHelper.sendTextMessage(room!!, "My Message", 1).first().eventId + assertTrue(room?.roomCryptoService()?.isEncrypted() == true) - // Open a new sessionx + val sentEvent = commonTestHelper.sendTextMessage(room!!, "My Message", 1).first() + val sentEventId = sentEvent.eventId + val sentEventText = sentEvent.getLastMessageContent()?.body - val aliceSession2 = commonTestHelper.logIntoAccount(aliceSession.myUserId, SessionTestParams(true)) + // Open a new session + val aliceSession2 = commonTestHelper.logIntoAccount(aliceSession.myUserId, SessionTestParams(false)) + // block key requesting for now as decrypt will send requests (room summary is trying to decrypt) + aliceSession2.cryptoService().enableKeyGossiping(false) + commonTestHelper.syncSession(aliceSession2) + + Log.v("TEST", "=======> AliceSession 2 is ${aliceSession2.sessionParams.deviceId}") val roomSecondSessionPOV = aliceSession2.getRoom(roomId) @@ -105,7 +104,10 @@ class KeyShareTests : InstrumentedTest { } val outgoingRequestsBefore = aliceSession2.cryptoService().getOutgoingRoomKeyRequests() + assertEquals("There should be no request as it's disabled", 0, outgoingRequestsBefore.size) + // Try to request + aliceSession2.cryptoService().enableKeyGossiping(true) aliceSession2.cryptoService().requestRoomKeyForEvent(receivedEvent.root) val eventMegolmSessionId = receivedEvent.root.content.toModel()?.sessionId @@ -115,10 +117,6 @@ class KeyShareTests : InstrumentedTest { commonTestHelper.waitWithLatch { latch -> commonTestHelper.retryPeriodicallyWithLatch(latch) { aliceSession2.cryptoService().getOutgoingRoomKeyRequests() - .filter { req -> - // filter out request that was known before - !outgoingRequestsBefore.any { req.requestId == it.requestId } - } .let { val outgoing = it.firstOrNull { it.sessionId == eventMegolmSessionId } outGoingRequestId = outgoing?.requestId @@ -139,17 +137,34 @@ class KeyShareTests : InstrumentedTest { commonTestHelper.waitWithLatch { latch -> commonTestHelper.retryPeriodicallyWithLatch(latch) { // DEBUG LOGS - aliceSession.cryptoService().getIncomingRoomKeyRequests().let { - Log.v("TEST", "Incoming request Session 1 (looking for $outGoingRequestId)") +// aliceSession.cryptoService().getIncomingRoomKeyRequests().let { +// Log.v("TEST", "Incoming request Session 1 (looking for $outGoingRequestId)") +// Log.v("TEST", "=========================") +// it.forEach { keyRequest -> +// Log.v("TEST", "[ts${keyRequest.localCreationTimestamp}] requestId ${keyRequest.requestId}, for sessionId ${keyRequest.requestBody?.sessionId}") +// } +// Log.v("TEST", "=========================") +// } + + val incoming = aliceSession.cryptoService().getIncomingRoomKeyRequests().firstOrNull { it.requestId == outGoingRequestId } + incoming != null + } + } + + commonTestHelper.waitWithLatch { latch -> + commonTestHelper.retryPeriodicallyWithLatch(latch) { + // DEBUG LOGS + aliceSession2.cryptoService().getOutgoingRoomKeyRequests().forEach { keyRequest -> Log.v("TEST", "=========================") - it.forEach { keyRequest -> - Log.v("TEST", "[ts${keyRequest.localCreationTimestamp}] requestId ${keyRequest.requestId}, for sessionId ${keyRequest.requestBody?.sessionId} is ${keyRequest.state}") - } + Log.v("TEST", "requestId ${keyRequest.requestId}, for sessionId ${keyRequest.requestBody?.sessionId}") + Log.v("TEST", "replies -> ${keyRequest.results.joinToString { it.toString() }}") Log.v("TEST", "=========================") } - val incoming = aliceSession.cryptoService().getIncomingRoomKeyRequests().firstOrNull { it.requestId == outGoingRequestId } - incoming?.state == GossipingRequestState.REJECTED + val outgoing = aliceSession2.cryptoService().getOutgoingRoomKeyRequests().firstOrNull { it.requestId == outGoingRequestId } + val reply = outgoing?.results?.firstOrNull { it.userId == aliceSession.myUserId && it.fromDevice == aliceSession.sessionParams.deviceId } + val resultCode = (reply?.result as? RequestResult.Failure)?.code + resultCode == WithHeldCode.UNVERIFIED } } @@ -162,255 +177,309 @@ class KeyShareTests : InstrumentedTest { } // Mark the device as trusted - aliceSession.cryptoService().setDeviceVerification(DeviceTrustLevel(crossSigningVerified = false, locallyVerified = true), aliceSession.myUserId, - aliceSession2.sessionParams.deviceId ?: "") + aliceSession.cryptoService().setDeviceVerification( + DeviceTrustLevel(crossSigningVerified = false, locallyVerified = true), aliceSession.myUserId, + aliceSession2.sessionParams.deviceId ?: "" + ) // Re request aliceSession2.cryptoService().reRequestRoomKeyForEvent(receivedEvent.root) - commonTestHelper.waitWithLatch { latch -> - commonTestHelper.retryPeriodicallyWithLatch(latch) { - aliceSession.cryptoService().getIncomingRoomKeyRequests().let { - Log.v("TEST", "Incoming request Session 1") - Log.v("TEST", "=========================") - it.forEach { - Log.v("TEST", "requestId ${it.requestId}, for sessionId ${it.requestBody?.sessionId} is ${it.state}") - } - Log.v("TEST", "=========================") - - it.any { it.requestBody?.sessionId == eventMegolmSessionId && it.state == GossipingRequestState.ACCEPTED } - } - } - } - - Thread.sleep(6_000) - commonTestHelper.waitWithLatch { latch -> - commonTestHelper.retryPeriodicallyWithLatch(latch) { - aliceSession2.cryptoService().getOutgoingRoomKeyRequests().let { - it.any { it.requestBody?.sessionId == eventMegolmSessionId && it.state == OutgoingGossipingRequestState.CANCELLED } - } - } - } - - try { - commonTestHelper.runBlockingTest { - aliceSession2.cryptoService().decryptEvent(receivedEvent.root, "foo") - } - } catch (failure: Throwable) { - fail("should have been able to decrypt") - } + cryptoTestHelper.ensureCanDecrypt(listOf(receivedEvent.eventId), aliceSession2, roomId, listOf(sentEventText ?: "")) commonTestHelper.signOutAndClose(aliceSession) commonTestHelper.signOutAndClose(aliceSession2) } + // See E2ESanityTest for a test regarding secret sharing + + /** + * Test that the sender of a message accepts to re-share to another user + * if the key was originally shared with him + */ @Test - @Ignore("This test will be ignored until it is fixed") - fun test_ShareSSSSSecret() { - val aliceSession1 = commonTestHelper.createAccount(TestConstants.USER_ALICE, SessionTestParams(true)) + fun test_reShareIfWasIntendedToBeShared() { + val commonTestHelper = CommonTestHelper(context()) + val cryptoTestHelper = CryptoTestHelper(commonTestHelper) - commonTestHelper.doSync { - aliceSession1.cryptoService().crossSigningService() - .initializeCrossSigning( - object : UserInteractiveAuthInterceptor { - override fun performStage(flowResponse: RegistrationFlowResponse, errCode: String?, promise: Continuation) { - promise.resume( - UserPasswordAuth( - user = aliceSession1.myUserId, - password = TestConstants.PASSWORD - ) - ) - } - }, it) - } + val testData = cryptoTestHelper.doE2ETestWithAliceAndBobInARoom(true) + val aliceSession = testData.firstSession + val roomFromAlice = aliceSession.getRoom(testData.roomId)!! + val bobSession = testData.secondSession!! - // Also bootstrap keybackup on first session - val creationInfo = commonTestHelper.doSync { - aliceSession1.cryptoService().keysBackupService().prepareKeysBackupVersion(null, null, it) - } - val version = commonTestHelper.doSync { - aliceSession1.cryptoService().keysBackupService().createKeysBackupVersion(creationInfo, it) - } - // Save it for gossiping - aliceSession1.cryptoService().keysBackupService().saveBackupRecoveryKey(creationInfo.recoveryKey, version = version.version) + val sentEvent = commonTestHelper.sendTextMessage(roomFromAlice, "Hello", 1).first() + val sentEventMegolmSession = sentEvent.root.content.toModel()!!.sessionId!! - val aliceSession2 = commonTestHelper.logIntoAccount(aliceSession1.myUserId, SessionTestParams(true)) + // bob should be able to decrypt + cryptoTestHelper.ensureCanDecrypt(listOf(sentEvent.eventId), bobSession, testData.roomId, listOf(sentEvent.getLastMessageContent()?.body ?: "")) - val aliceVerificationService1 = aliceSession1.cryptoService().verificationService() - val aliceVerificationService2 = aliceSession2.cryptoService().verificationService() - - // force keys download - commonTestHelper.doSync> { - aliceSession1.cryptoService().downloadKeys(listOf(aliceSession1.myUserId), true, it) - } - commonTestHelper.doSync> { - aliceSession2.cryptoService().downloadKeys(listOf(aliceSession2.myUserId), true, it) - } - - var session1ShortCode: String? = null - var session2ShortCode: String? = null - - aliceVerificationService1.addListener(object : VerificationService.Listener { - override fun transactionUpdated(tx: VerificationTransaction) { - Log.d("#TEST", "AA: tx incoming?:${tx.isIncoming} state ${tx.state}") - if (tx is SasVerificationTransaction) { - if (tx.state == VerificationTxState.OnStarted) { - (tx as IncomingSasVerificationTransaction).performAccept() - } - if (tx.state == VerificationTxState.ShortCodeReady) { - session1ShortCode = tx.getDecimalCodeRepresentation() - Thread.sleep(500) - tx.userHasVerifiedShortCode() - } - } - } - }) - - aliceVerificationService2.addListener(object : VerificationService.Listener { - override fun transactionUpdated(tx: VerificationTransaction) { - Log.d("#TEST", "BB: tx incoming?:${tx.isIncoming} state ${tx.state}") - if (tx is SasVerificationTransaction) { - if (tx.state == VerificationTxState.ShortCodeReady) { - session2ShortCode = tx.getDecimalCodeRepresentation() - Thread.sleep(500) - tx.userHasVerifiedShortCode() - } - } - } - }) - - val txId = "m.testVerif12" - aliceVerificationService2.beginKeyVerification(VerificationMethod.SAS, aliceSession1.myUserId, aliceSession1.sessionParams.deviceId - ?: "", txId) + // Let's try to request any how. + // As it was share previously alice should accept to reshare + bobSession.cryptoService().reRequestRoomKeyForEvent(sentEvent.root) commonTestHelper.waitWithLatch { latch -> commonTestHelper.retryPeriodicallyWithLatch(latch) { - aliceSession1.cryptoService().getDeviceInfo(aliceSession1.myUserId, aliceSession2.sessionParams.deviceId ?: "")?.isVerified == true + val outgoing = bobSession.cryptoService().getOutgoingRoomKeyRequests().firstOrNull { it.sessionId == sentEventMegolmSession } + val aliceReply = outgoing?.results?.firstOrNull { it.userId == aliceSession.myUserId && it.fromDevice == aliceSession.sessionParams.deviceId } + aliceReply != null && aliceReply.result is RequestResult.Success } } - - assertNotNull(session1ShortCode) - Log.d("#TEST", "session1ShortCode: $session1ShortCode") - assertNotNull(session2ShortCode) - Log.d("#TEST", "session2ShortCode: $session2ShortCode") - assertEquals(session1ShortCode, session2ShortCode) - - // SSK and USK private keys should have been shared - - commonTestHelper.waitWithLatch(60_000) { latch -> - commonTestHelper.retryPeriodicallyWithLatch(latch) { - Log.d("#TEST", "CAN XS :${aliceSession2.cryptoService().crossSigningService().getMyCrossSigningKeys()}") - aliceSession2.cryptoService().crossSigningService().canCrossSign() - } - } - - // Test that key backup key has been shared to - commonTestHelper.waitWithLatch(60_000) { latch -> - val keysBackupService = aliceSession2.cryptoService().keysBackupService() - commonTestHelper.retryPeriodicallyWithLatch(latch) { - Log.d("#TEST", "Recovery :${keysBackupService.getKeyBackupRecoveryKeyInfo()?.recoveryKey}") - keysBackupService.getKeyBackupRecoveryKeyInfo()?.recoveryKey == creationInfo.recoveryKey - } - } - - commonTestHelper.signOutAndClose(aliceSession1) - commonTestHelper.signOutAndClose(aliceSession2) } + /** + * Test that our own devices accept to reshare to unverified device if it was shared initialy + * if the key was originally shared with him + */ @Test - @Ignore("This test will be ignored until it is fixed") - fun test_ImproperKeyShareBug() { - val aliceSession = commonTestHelper.createAccount(TestConstants.USER_ALICE, SessionTestParams(true)) + fun test_reShareToUnverifiedIfWasIntendedToBeShared() { + val commonTestHelper = CommonTestHelper(context()) + val cryptoTestHelper = CryptoTestHelper(commonTestHelper) - commonTestHelper.doSync { - aliceSession.cryptoService().crossSigningService() - .initializeCrossSigning( - object : UserInteractiveAuthInterceptor { - override fun performStage(flowResponse: RegistrationFlowResponse, errCode: String?, promise: Continuation) { - promise.resume( - UserPasswordAuth( - user = aliceSession.myUserId, - password = TestConstants.PASSWORD, - session = flowResponse.session - ) - ) - } - }, it) + val testData = cryptoTestHelper.doE2ETestWithAliceInARoom(true) + val aliceSession = testData.firstSession + val roomFromAlice = aliceSession.getRoom(testData.roomId)!! + + val aliceNewSession = commonTestHelper.logIntoAccount(aliceSession.myUserId, SessionTestParams(true)) + + // we wait for alice first session to be aware of that session? + commonTestHelper.waitWithLatch { latch -> + commonTestHelper.retryPeriodicallyWithLatch(latch) { + val newSession = aliceSession.cryptoService().getUserDevices(aliceSession.myUserId) + .firstOrNull { it.deviceId == aliceNewSession.sessionParams.deviceId } + newSession != null + } } + val sentEvent = commonTestHelper.sendTextMessage(roomFromAlice, "Hello", 1).first() + val sentEventMegolmSession = sentEvent.root.content.toModel()!!.sessionId!! - // Create an encrypted room and send a couple of messages - val roomId = commonTestHelper.runBlockingTest { - aliceSession.createRoom( - CreateRoomParams().apply { - visibility = RoomDirectoryVisibility.PRIVATE - enableEncryption() - } - ) + // Let's try to request any how. + // As it was share previously alice should accept to reshare + aliceNewSession.cryptoService().reRequestRoomKeyForEvent(sentEvent.root) + + commonTestHelper.waitWithLatch { latch -> + commonTestHelper.retryPeriodicallyWithLatch(latch) { + val outgoing = aliceNewSession.cryptoService().getOutgoingRoomKeyRequests().firstOrNull { it.sessionId == sentEventMegolmSession } + val ownDeviceReply = + outgoing?.results?.firstOrNull { it.userId == aliceSession.myUserId && it.fromDevice == aliceSession.sessionParams.deviceId } + ownDeviceReply != null && ownDeviceReply.result is RequestResult.Success + } } - val roomAlicePov = aliceSession.getRoom(roomId) - assertNotNull(roomAlicePov) - Thread.sleep(1_000) - assertTrue(roomAlicePov?.isEncrypted() == true) - val secondEventId = commonTestHelper.sendTextMessage(roomAlicePov!!, "Message", 3)[1].eventId + } - // Create bob session + /** + * Tests that keys reshared with own verified session are done from the earliest known index + */ + @Test + fun test_reShareFromTheEarliestKnownIndexWithOwnVerifiedSession() { + val commonTestHelper = CommonTestHelper(context()) + val cryptoTestHelper = CryptoTestHelper(commonTestHelper) - val bobSession = commonTestHelper.createAccount(TestConstants.USER_BOB, SessionTestParams(true)) - commonTestHelper.doSync { - bobSession.cryptoService().crossSigningService() - .initializeCrossSigning( - object : UserInteractiveAuthInterceptor { - override fun performStage(flowResponse: RegistrationFlowResponse, errCode: String?, promise: Continuation) { - promise.resume( - UserPasswordAuth( - user = bobSession.myUserId, - password = TestConstants.PASSWORD, - session = flowResponse.session - ) - ) - } - }, it) - } + val testData = cryptoTestHelper.doE2ETestWithAliceAndBobInARoom(true) + val aliceSession = testData.firstSession + val bobSession = testData.secondSession!! + val roomFromBob = bobSession.getRoom(testData.roomId)!! - // Let alice invite bob - commonTestHelper.runBlockingTest { - roomAlicePov.invite(bobSession.myUserId, null) - } + val sentEvents = commonTestHelper.sendTextMessage(roomFromBob, "Hello", 3) + val sentEventMegolmSession = sentEvents.first().root.content.toModel()!!.sessionId!! - commonTestHelper.runBlockingTest { - bobSession.joinRoom(roomAlicePov.roomId, null, emptyList()) - } + // Let alice now add a new session + val aliceNewSession = commonTestHelper.logIntoAccount(aliceSession.myUserId, SessionTestParams(false)) + aliceNewSession.cryptoService().enableKeyGossiping(false) + commonTestHelper.syncSession(aliceNewSession) - // we want to discard alice outbound session - aliceSession.cryptoService().discardOutboundSession(roomAlicePov.roomId) - - // and now resend a new message to reset index to 0 - commonTestHelper.sendTextMessage(roomAlicePov, "After", 1) - - val roomRoomBobPov = aliceSession.getRoom(roomId) - val beforeJoin = roomRoomBobPov!!.getTimelineEvent(secondEventId) - - var dRes = tryOrNull { - commonTestHelper.runBlockingTest { - bobSession.cryptoService().decryptEvent(beforeJoin!!.root, "") + // we wait bob first session to be aware of that session? + commonTestHelper.waitWithLatch { latch -> + commonTestHelper.retryPeriodicallyWithLatch(latch) { + val newSession = bobSession.cryptoService().getUserDevices(aliceSession.myUserId) + .firstOrNull { it.deviceId == aliceNewSession.sessionParams.deviceId } + newSession != null } } - assert(dRes == null) + val newEvent = commonTestHelper.sendTextMessage(roomFromBob, "The New", 1).first() + val newEventId = newEvent.eventId + val newEventText = newEvent.getLastMessageContent()!!.body - // Try to re-ask the keys + // alice should be able to decrypt the new one + cryptoTestHelper.ensureCanDecrypt(listOf(newEventId), aliceNewSession, testData.roomId, listOf(newEventText)) + // but not the first one! + cryptoTestHelper.ensureCannotDecrypt(sentEvents.map { it.eventId }, aliceNewSession, testData.roomId) - bobSession.cryptoService().reRequestRoomKeyForEvent(beforeJoin!!.root) + // All should be using the same session id + sentEvents.forEach { + assertEquals(sentEventMegolmSession, it.root.content.toModel()!!.sessionId) + } + assertEquals(sentEventMegolmSession, newEvent.root.content.toModel()!!.sessionId) - Thread.sleep(3_000) + // Request a first time, bob should reply with unauthorized and alice should reply with unverified + aliceNewSession.cryptoService().enableKeyGossiping(true) + aliceNewSession.cryptoService().reRequestRoomKeyForEvent(newEvent.root) - // With the bug the first session would have improperly reshare that key :/ - dRes = tryOrNull { - commonTestHelper.runBlockingTest { - bobSession.cryptoService().decryptEvent(beforeJoin.root, "") + commonTestHelper.waitWithLatch { latch -> + commonTestHelper.retryPeriodicallyWithLatch(latch) { + val outgoing = aliceNewSession.cryptoService().getOutgoingRoomKeyRequests().firstOrNull { it.sessionId == sentEventMegolmSession } + val ownDeviceReply = outgoing?.results + ?.firstOrNull { it.userId == aliceSession.myUserId && it.fromDevice == aliceSession.sessionParams.deviceId } + val result = ownDeviceReply?.result + Log.v("TEST", "own device result is $result") + result != null && result is RequestResult.Failure && result.code == WithHeldCode.UNVERIFIED } } - Log.d("#TEST", "KS: sgould not decrypt that ${beforeJoin.root.getClearContent().toModel()?.body}") - assert(dRes?.clearEvent == null) + + commonTestHelper.waitWithLatch { latch -> + commonTestHelper.retryPeriodicallyWithLatch(latch) { + val outgoing = aliceNewSession.cryptoService().getOutgoingRoomKeyRequests().firstOrNull { it.sessionId == sentEventMegolmSession } + val bobDeviceReply = outgoing?.results + ?.firstOrNull { it.userId == bobSession.myUserId && it.fromDevice == bobSession.sessionParams.deviceId } + val result = bobDeviceReply?.result + Log.v("TEST", "bob device result is $result") + result != null && result is RequestResult.Success && result.chainIndex > 0 + } + } + + // it's a success but still can't decrypt first message + cryptoTestHelper.ensureCannotDecrypt(sentEvents.map { it.eventId }, aliceNewSession, testData.roomId) + + // Mark the new session as verified + aliceSession.cryptoService() + .verificationService() + .markedLocallyAsManuallyVerified(aliceNewSession.myUserId, aliceNewSession.sessionParams.deviceId!!) + + // Let's now try to request + aliceNewSession.cryptoService().reRequestRoomKeyForEvent(sentEvents.first().root) + + commonTestHelper.waitWithLatch { latch -> + commonTestHelper.retryPeriodicallyWithLatch(latch) { + // DEBUG LOGS + aliceNewSession.cryptoService().getOutgoingRoomKeyRequests().forEach { keyRequest -> + Log.v("TEST", "=========================") + Log.v("TEST", "requestId ${keyRequest.requestId}, for sessionId ${keyRequest.requestBody?.sessionId}") + Log.v("TEST", "replies -> ${keyRequest.results.joinToString { it.toString() }}") + Log.v("TEST", "=========================") + } + val outgoing = aliceNewSession.cryptoService().getOutgoingRoomKeyRequests().firstOrNull { it.sessionId == sentEventMegolmSession } + val ownDeviceReply = + outgoing?.results?.firstOrNull { it.userId == aliceSession.myUserId && it.fromDevice == aliceSession.sessionParams.deviceId } + val result = ownDeviceReply?.result + result != null && result is RequestResult.Success && result.chainIndex == 0 + } + } + + // now the new session should be able to decrypt all! + cryptoTestHelper.ensureCanDecrypt( + sentEvents.map { it.eventId }, + aliceNewSession, + testData.roomId, + sentEvents.map { it.getLastMessageContent()!!.body } + ) + + // Additional test, can we check that bob replied successfully but with a ratcheted key + commonTestHelper.waitWithLatch { latch -> + commonTestHelper.retryPeriodicallyWithLatch(latch) { + val outgoing = aliceNewSession.cryptoService().getOutgoingRoomKeyRequests().firstOrNull { it.sessionId == sentEventMegolmSession } + val bobReply = outgoing?.results?.firstOrNull { it.userId == bobSession.myUserId } + val result = bobReply?.result + result != null && result is RequestResult.Success && result.chainIndex == 3 + } + } + + commonTestHelper.signOutAndClose(aliceNewSession) + commonTestHelper.signOutAndClose(aliceSession) + commonTestHelper.signOutAndClose(bobSession) + } + + /** + * Tests that we don't cancel a request to early on first forward if the index is not good enough + */ + @Test + fun test_dontCancelToEarly() { + val commonTestHelper = CommonTestHelper(context()) + val cryptoTestHelper = CryptoTestHelper(commonTestHelper) + + val testData = cryptoTestHelper.doE2ETestWithAliceAndBobInARoom(true) + val aliceSession = testData.firstSession + val bobSession = testData.secondSession!! + val roomFromBob = bobSession.getRoom(testData.roomId)!! + + val sentEvents = commonTestHelper.sendTextMessage(roomFromBob, "Hello", 3) + val sentEventMegolmSession = sentEvents.first().root.content.toModel()!!.sessionId!! + + // Let alice now add a new session + val aliceNewSession = commonTestHelper.logIntoAccount(aliceSession.myUserId, SessionTestParams(true)) + + // we wait bob first session to be aware of that session? + commonTestHelper.waitWithLatch { latch -> + commonTestHelper.retryPeriodicallyWithLatch(latch) { + val newSession = bobSession.cryptoService().getUserDevices(aliceSession.myUserId) + .firstOrNull { it.deviceId == aliceNewSession.sessionParams.deviceId } + newSession != null + } + } + + val newEvent = commonTestHelper.sendTextMessage(roomFromBob, "The New", 1).first() + val newEventId = newEvent.eventId + val newEventText = newEvent.getLastMessageContent()!!.body + + // alice should be able to decrypt the new one + cryptoTestHelper.ensureCanDecrypt(listOf(newEventId), aliceNewSession, testData.roomId, listOf(newEventText)) + // but not the first one! + cryptoTestHelper.ensureCannotDecrypt(sentEvents.map { it.eventId }, aliceNewSession, testData.roomId) + + // All should be using the same session id + sentEvents.forEach { + assertEquals(sentEventMegolmSession, it.root.content.toModel()!!.sessionId) + } + assertEquals(sentEventMegolmSession, newEvent.root.content.toModel()!!.sessionId) + + // Mark the new session as verified + aliceSession.cryptoService() + .verificationService() + .markedLocallyAsManuallyVerified(aliceNewSession.myUserId, aliceNewSession.sessionParams.deviceId!!) + + // /!\ Stop initial alice session syncing so that it can't reply + aliceSession.cryptoService().enableKeyGossiping(false) + aliceSession.stopSync() + + // Let's now try to request + aliceNewSession.cryptoService().reRequestRoomKeyForEvent(sentEvents.first().root) + + // Should get a reply from bob and not from alice + commonTestHelper.waitWithLatch { latch -> + commonTestHelper.retryPeriodicallyWithLatch(latch) { + // Log.d("#TEST", "outgoing key requests :${aliceNewSession.cryptoService().getOutgoingRoomKeyRequests().joinToString { it.sessionId ?: "?" }}") + val outgoing = aliceNewSession.cryptoService().getOutgoingRoomKeyRequests().firstOrNull { it.sessionId == sentEventMegolmSession } + val bobReply = outgoing?.results?.firstOrNull { it.userId == bobSession.myUserId } + val result = bobReply?.result + result != null && result is RequestResult.Success && result.chainIndex == 3 + } + } + + val outgoingReq = aliceNewSession.cryptoService().getOutgoingRoomKeyRequests().firstOrNull { it.sessionId == sentEventMegolmSession } + + assertNull("We should not have a reply from first session", outgoingReq!!.results.firstOrNull { it.fromDevice == aliceSession.sessionParams.deviceId }) + assertEquals("The request should not be canceled", OutgoingRoomKeyRequestState.SENT, outgoingReq.state) + + // let's wake up alice + aliceSession.cryptoService().enableKeyGossiping(true) + aliceSession.startSync(true) + + // We should now get a reply from first session + commonTestHelper.waitWithLatch { latch -> + commonTestHelper.retryPeriodicallyWithLatch(latch) { + val outgoing = aliceNewSession.cryptoService().getOutgoingRoomKeyRequests().firstOrNull { it.sessionId == sentEventMegolmSession } + val ownDeviceReply = + outgoing?.results?.firstOrNull { it.userId == aliceSession.myUserId && it.fromDevice == aliceSession.sessionParams.deviceId } + val result = ownDeviceReply?.result + result != null && result is RequestResult.Success && result.chainIndex == 0 + } + } + + // It should be in sent then cancel + val outgoing = aliceNewSession.cryptoService().getOutgoingRoomKeyRequests().firstOrNull { it.sessionId == sentEventMegolmSession } + assertEquals("The request should be canceled", OutgoingRoomKeyRequestState.SENT_THEN_CANCELED, outgoing!!.state) + + commonTestHelper.signOutAndClose(aliceNewSession) + commonTestHelper.signOutAndClose(aliceSession) + commonTestHelper.signOutAndClose(bobSession) } } diff --git a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/gossiping/WithHeldTests.kt b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/gossiping/WithHeldTests.kt index 55bb03278c..1c3c6c46e7 100644 --- a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/gossiping/WithHeldTests.kt +++ b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/gossiping/WithHeldTests.kt @@ -21,7 +21,7 @@ import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.LargeTest import org.junit.Assert import org.junit.FixMethodOrder -import org.junit.Ignore +import org.junit.Rule import org.junit.Test import org.junit.runner.RunWith import org.junit.runners.MethodSorters @@ -29,13 +29,17 @@ import org.matrix.android.sdk.InstrumentedTest import org.matrix.android.sdk.api.NoOpMatrixCallback import org.matrix.android.sdk.api.extensions.tryOrNull import org.matrix.android.sdk.api.session.crypto.MXCryptoError +import org.matrix.android.sdk.api.session.crypto.RequestResult import org.matrix.android.sdk.api.session.events.model.EventType import org.matrix.android.sdk.api.session.events.model.content.EncryptedEventContent import org.matrix.android.sdk.api.session.events.model.content.WithHeldCode import org.matrix.android.sdk.api.session.events.model.toModel +import org.matrix.android.sdk.api.session.getRoom +import org.matrix.android.sdk.api.session.room.getTimelineEvent import org.matrix.android.sdk.common.CommonTestHelper import org.matrix.android.sdk.common.CryptoTestHelper import org.matrix.android.sdk.common.MockOkHttpInterceptor +import org.matrix.android.sdk.common.RetryTestRule import org.matrix.android.sdk.common.SessionTestParams import org.matrix.android.sdk.common.TestConstants @@ -44,12 +48,13 @@ import org.matrix.android.sdk.common.TestConstants @LargeTest class WithHeldTests : InstrumentedTest { - private val testHelper = CommonTestHelper(context()) - private val cryptoTestHelper = CryptoTestHelper(testHelper) + @get:Rule val rule = RetryTestRule(3) @Test - @Ignore("This test will be ignored until it is fixed") fun test_WithHeldUnverifiedReason() { + val testHelper = CommonTestHelper(context()) + val cryptoTestHelper = CryptoTestHelper(testHelper) + // ============================= // ARRANGE // ============================= @@ -67,7 +72,6 @@ class WithHeldTests : InstrumentedTest { val roomAlicePOV = aliceSession.getRoom(roomId)!! val bobUnverifiedSession = testHelper.logIntoAccount(bobSession.myUserId, SessionTestParams(true)) - // ============================= // ACT // ============================= @@ -86,6 +90,7 @@ class WithHeldTests : InstrumentedTest { val eventBobPOV = bobUnverifiedSession.getRoom(roomId)?.getTimelineEvent(timelineEvent.eventId)!! + val megolmSessionId = eventBobPOV.root.content.toModel()!!.sessionId!! // ============================= // ASSERT // ============================= @@ -104,6 +109,20 @@ class WithHeldTests : InstrumentedTest { Assert.assertEquals("Cause should be unverified", WithHeldCode.UNVERIFIED.value, technicalMessage) } + // Let's see if the reply we got from bob first session is unverified + testHelper.waitWithLatch { latch -> + testHelper.retryPeriodicallyWithLatch(latch) { + bobUnverifiedSession.cryptoService().getOutgoingRoomKeyRequests() + .firstOrNull { it.sessionId == megolmSessionId } + ?.results + ?.firstOrNull { it.fromDevice == bobSession.sessionParams.deviceId } + ?.result + ?.let { + it as? RequestResult.Failure + } + ?.code == WithHeldCode.UNVERIFIED + } + } // enable back sending to unverified aliceSession.cryptoService().setGlobalBlacklistUnverifiedDevices(false) @@ -137,21 +156,25 @@ class WithHeldTests : InstrumentedTest { } @Test - @Ignore("This test will be ignored until it is fixed") fun test_WithHeldNoOlm() { + val testHelper = CommonTestHelper(context()) + val cryptoTestHelper = CryptoTestHelper(testHelper) + val testData = cryptoTestHelper.doE2ETestWithAliceAndBobInARoom() val aliceSession = testData.firstSession val bobSession = testData.secondSession!! val aliceInterceptor = testHelper.getTestInterceptor(aliceSession) // Simulate no OTK - aliceInterceptor!!.addRule(MockOkHttpInterceptor.SimpleRule( - "/keys/claim", - 200, - """ + aliceInterceptor!!.addRule( + MockOkHttpInterceptor.SimpleRule( + "/keys/claim", + 200, + """ { "one_time_keys" : {} } """ - )) + ) + ) Log.d("#TEST", "Recovery :${aliceSession.sessionParams.credentials.accessToken}") val roomAlicePov = aliceSession.getRoom(testData.roomId)!! @@ -182,7 +205,10 @@ class WithHeldTests : InstrumentedTest { // Ensure that alice has marked the session to be shared with bob val sessionId = eventBobPOV!!.root.content.toModel()!!.sessionId!! - val chainIndex = aliceSession.cryptoService().getSharedWithInfo(testData.roomId, sessionId).getObject(bobSession.myUserId, bobSession.sessionParams.credentials.deviceId) + val chainIndex = aliceSession.cryptoService().getSharedWithInfo(testData.roomId, sessionId).getObject( + bobSession.myUserId, + bobSession.sessionParams.credentials.deviceId + ) Assert.assertEquals("Alice should have marked bob's device for this session", 0, chainIndex) // Add a new device for bob @@ -200,7 +226,10 @@ class WithHeldTests : InstrumentedTest { } } - val chainIndex2 = aliceSession.cryptoService().getSharedWithInfo(testData.roomId, sessionId).getObject(bobSecondSession.myUserId, bobSecondSession.sessionParams.credentials.deviceId) + val chainIndex2 = aliceSession.cryptoService().getSharedWithInfo(testData.roomId, sessionId).getObject( + bobSecondSession.myUserId, + bobSecondSession.sessionParams.credentials.deviceId + ) Assert.assertEquals("Alice should have marked bob's device for this session", 1, chainIndex2) @@ -210,8 +239,10 @@ class WithHeldTests : InstrumentedTest { } @Test - @Ignore("This test will be ignored until it is fixed") fun test_WithHeldKeyRequest() { + val testHelper = CommonTestHelper(context()) + val cryptoTestHelper = CryptoTestHelper(testHelper) + val testData = cryptoTestHelper.doE2ETestWithAliceAndBobInARoom() val aliceSession = testData.firstSession val bobSession = testData.secondSession!! @@ -257,5 +288,8 @@ class WithHeldTests : InstrumentedTest { wc?.code == WithHeldCode.UNAUTHORISED } } + + testHelper.signOutAndClose(aliceSession) + testHelper.signOutAndClose(bobSecondSession) } } diff --git a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/keysbackup/KeysBackupPasswordTest.kt b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/keysbackup/KeysBackupPasswordTest.kt index d035fe5fde..9bf08f6fc0 100644 --- a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/keysbackup/KeysBackupPasswordTest.kt +++ b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/keysbackup/KeysBackupPasswordTest.kt @@ -54,9 +54,11 @@ class KeysBackupPasswordTest : InstrumentedTest { assertEquals(OlmPkDecryption.privateKeyLength(), generatePrivateKeyResult.privateKey.size) // Reverse operation - val retrievedPrivateKey = retrievePrivateKeyWithPassword(PASSWORD, + val retrievedPrivateKey = retrievePrivateKeyWithPassword( + PASSWORD, generatePrivateKeyResult.salt, - generatePrivateKeyResult.iterations) + generatePrivateKeyResult.iterations + ) assertEquals(OlmPkDecryption.privateKeyLength(), retrievedPrivateKey.size) assertArrayEquals(generatePrivateKeyResult.privateKey, retrievedPrivateKey) @@ -102,9 +104,11 @@ class KeysBackupPasswordTest : InstrumentedTest { assertEquals(OlmPkDecryption.privateKeyLength(), generatePrivateKeyResult.privateKey.size) // Reverse operation, with bad password - val retrievedPrivateKey = retrievePrivateKeyWithPassword(BAD_PASSWORD, + val retrievedPrivateKey = retrievePrivateKeyWithPassword( + BAD_PASSWORD, generatePrivateKeyResult.salt, - generatePrivateKeyResult.iterations) + generatePrivateKeyResult.iterations + ) assertEquals(OlmPkDecryption.privateKeyLength(), retrievedPrivateKey.size) assertByteArrayNotEqual(generatePrivateKeyResult.privateKey, retrievedPrivateKey) @@ -122,9 +126,11 @@ class KeysBackupPasswordTest : InstrumentedTest { assertEquals(OlmPkDecryption.privateKeyLength(), generatePrivateKeyResult.privateKey.size) // Reverse operation, with bad iteration - val retrievedPrivateKey = retrievePrivateKeyWithPassword(PASSWORD, + val retrievedPrivateKey = retrievePrivateKeyWithPassword( + PASSWORD, generatePrivateKeyResult.salt, - 500_001) + 500_001 + ) assertEquals(OlmPkDecryption.privateKeyLength(), retrievedPrivateKey.size) assertByteArrayNotEqual(generatePrivateKeyResult.privateKey, retrievedPrivateKey) @@ -142,9 +148,11 @@ class KeysBackupPasswordTest : InstrumentedTest { assertEquals(OlmPkDecryption.privateKeyLength(), generatePrivateKeyResult.privateKey.size) // Reverse operation, with bad iteration - val retrievedPrivateKey = retrievePrivateKeyWithPassword(PASSWORD, + val retrievedPrivateKey = retrievePrivateKeyWithPassword( + PASSWORD, BAD_SALT, - generatePrivateKeyResult.iterations) + generatePrivateKeyResult.iterations + ) assertEquals(OlmPkDecryption.privateKeyLength(), retrievedPrivateKey.size) assertByteArrayNotEqual(generatePrivateKeyResult.privateKey, retrievedPrivateKey) @@ -168,7 +176,8 @@ class KeysBackupPasswordTest : InstrumentedTest { 116.toByte(), 224.toByte(), 229.toByte(), 224.toByte(), 9.toByte(), 3.toByte(), 178.toByte(), 162.toByte(), 120.toByte(), 23.toByte(), 108.toByte(), 218.toByte(), 22.toByte(), 61.toByte(), 241.toByte(), 200.toByte(), 235.toByte(), 173.toByte(), 236.toByte(), 100.toByte(), 115.toByte(), 247.toByte(), 33.toByte(), 132.toByte(), - 195.toByte(), 154.toByte(), 64.toByte(), 158.toByte(), 184.toByte(), 148.toByte(), 20.toByte(), 85.toByte()) + 195.toByte(), 154.toByte(), 64.toByte(), 158.toByte(), 184.toByte(), 148.toByte(), 20.toByte(), 85.toByte() + ) assertArrayEquals(privateKeyBytes, retrievedPrivateKey) } diff --git a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/keysbackup/KeysBackupTest.kt b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/keysbackup/KeysBackupTest.kt index 063c0c0ca3..9136272b1e 100644 --- a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/keysbackup/KeysBackupTest.kt +++ b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/keysbackup/KeysBackupTest.kt @@ -24,7 +24,6 @@ import org.junit.Assert.assertNotNull import org.junit.Assert.assertNull import org.junit.Assert.assertTrue import org.junit.FixMethodOrder -import org.junit.Ignore import org.junit.Test import org.junit.runner.RunWith import org.junit.runners.MethodSorters @@ -37,10 +36,13 @@ import org.matrix.android.sdk.api.session.crypto.keysbackup.KeysBackupLastVersio import org.matrix.android.sdk.api.session.crypto.keysbackup.KeysBackupState import org.matrix.android.sdk.api.session.crypto.keysbackup.KeysBackupStateListener import org.matrix.android.sdk.api.session.crypto.keysbackup.KeysBackupVersionTrust +import org.matrix.android.sdk.api.session.crypto.keysbackup.KeysBackupVersionTrustSignature import org.matrix.android.sdk.api.session.crypto.keysbackup.KeysVersion +import org.matrix.android.sdk.api.session.crypto.keysbackup.KeysVersionResult import org.matrix.android.sdk.api.session.crypto.keysbackup.MegolmBackupCreationInfo import org.matrix.android.sdk.api.session.crypto.keysbackup.toKeysVersionResult import org.matrix.android.sdk.api.session.crypto.model.ImportRoomKeysResult +import org.matrix.android.sdk.api.session.getRoom import org.matrix.android.sdk.common.CommonTestHelper import org.matrix.android.sdk.common.CryptoTestHelper import org.matrix.android.sdk.common.TestConstants @@ -53,18 +55,16 @@ import java.util.concurrent.CountDownLatch @LargeTest class KeysBackupTest : InstrumentedTest { - private val testHelper = CommonTestHelper(context()) - private val cryptoTestHelper = CryptoTestHelper(testHelper) - private val keysBackupTestHelper = KeysBackupTestHelper(testHelper, cryptoTestHelper) - /** * - From doE2ETestWithAliceAndBobInARoomWithEncryptedMessages, we should have no backed up keys * - Check backup keys after having marked one as backed up * - Reset keys backup markers */ @Test - @Ignore("This test will be ignored until it is fixed") fun roomKeysTest_testBackupStore_ok() { + val testHelper = CommonTestHelper(context()) + val cryptoTestHelper = CryptoTestHelper(testHelper) + val cryptoTestData = cryptoTestHelper.doE2ETestWithAliceAndBobInARoomWithEncryptedMessages() // From doE2ETestWithAliceAndBobInARoomWithEncryptedMessages, we should have no backed up keys @@ -103,6 +103,8 @@ class KeysBackupTest : InstrumentedTest { */ @Test fun prepareKeysBackupVersionTest() { + val testHelper = CommonTestHelper(context()) + val bobSession = testHelper.createAccount(TestConstants.USER_BOB, KeysBackupTestConstants.defaultSessionParams) assertNotNull(bobSession.cryptoService().keysBackupService()) @@ -131,7 +133,11 @@ class KeysBackupTest : InstrumentedTest { */ @Test fun createKeysBackupVersionTest() { + val testHelper = CommonTestHelper(context()) + val cryptoTestHelper = CryptoTestHelper(testHelper) + val bobSession = testHelper.createAccount(TestConstants.USER_BOB, KeysBackupTestConstants.defaultSessionParams) + cryptoTestHelper.initializeCrossSigning(bobSession) val keysBackup = bobSession.cryptoService().keysBackupService() @@ -146,13 +152,46 @@ class KeysBackupTest : InstrumentedTest { assertFalse(keysBackup.isEnabled) // Create the version - testHelper.doSync { + val version = testHelper.doSync { keysBackup.createKeysBackupVersion(megolmBackupCreationInfo, it) } // Backup must be enable now assertTrue(keysBackup.isEnabled) + // Check that it's signed with MSK + val versionResult = testHelper.doSync { + keysBackup.getVersion(version.version, it) + } + val trust = testHelper.doSync { + keysBackup.getKeysBackupTrust(versionResult!!, it) + } + + assertEquals("Should have 2 signatures", 2, trust.signatures.size) + + trust.signatures + .firstOrNull { it is KeysBackupVersionTrustSignature.DeviceSignature } + .let { + assertNotNull("Should be signed by a device", it) + it as KeysBackupVersionTrustSignature.DeviceSignature + }.let { + assertEquals("Should be signed by current device", bobSession.sessionParams.deviceId, it.deviceId) + assertTrue("Signature should be valid", it.valid) + } + + trust.signatures + .firstOrNull { it is KeysBackupVersionTrustSignature.UserSignature } + .let { + assertNotNull("Should be signed by a user", it) + it as KeysBackupVersionTrustSignature.UserSignature + }.let { + val msk = bobSession.cryptoService().crossSigningService() + .getMyCrossSigningKeys()?.masterKey()?.unpaddedBase64PublicKey + assertEquals("Should be signed by my msk 1", msk, it.keyId) + assertEquals("Should be signed by my msk 2", msk, it.cryptoCrossSigningKey?.unpaddedBase64PublicKey) + assertTrue("Signature should be valid", it.valid) + } + stateObserver.stopAndCheckStates(null) testHelper.signOutAndClose(bobSession) } @@ -162,8 +201,11 @@ class KeysBackupTest : InstrumentedTest { * - Check the backup completes */ @Test - @Ignore("This test will be ignored until it is fixed") fun backupAfterCreateKeysBackupVersionTest() { + val testHelper = CommonTestHelper(context()) + val cryptoTestHelper = CryptoTestHelper(testHelper) + val keysBackupTestHelper = KeysBackupTestHelper(testHelper, cryptoTestHelper) + val cryptoTestData = cryptoTestHelper.doE2ETestWithAliceAndBobInARoomWithEncryptedMessages() keysBackupTestHelper.waitForKeybackUpBatching() @@ -203,8 +245,11 @@ class KeysBackupTest : InstrumentedTest { * Check that backupAllGroupSessions() returns valid data */ @Test - @Ignore("This test will be ignored until it is fixed") fun backupAllGroupSessionsTest() { + val testHelper = CommonTestHelper(context()) + val cryptoTestHelper = CryptoTestHelper(testHelper) + val keysBackupTestHelper = KeysBackupTestHelper(testHelper, cryptoTestHelper) + val cryptoTestData = cryptoTestHelper.doE2ETestWithAliceAndBobInARoomWithEncryptedMessages() val keysBackup = cryptoTestData.firstSession.cryptoService().keysBackupService() @@ -248,8 +293,11 @@ class KeysBackupTest : InstrumentedTest { * - Compare the decrypted megolm key with the original one */ @Test - @Ignore("This test will be ignored until it is fixed") fun testEncryptAndDecryptKeysBackupData() { + val testHelper = CommonTestHelper(context()) + val cryptoTestHelper = CryptoTestHelper(testHelper) + val keysBackupTestHelper = KeysBackupTestHelper(testHelper, cryptoTestHelper) + val cryptoTestData = cryptoTestHelper.doE2ETestWithAliceAndBobInARoomWithEncryptedMessages() val keysBackup = cryptoTestData.firstSession.cryptoService().keysBackupService() as DefaultKeysBackupService @@ -271,10 +319,12 @@ class KeysBackupTest : InstrumentedTest { assertNotNull(decryption) // - Check decryptKeyBackupData() returns stg val sessionData = keysBackup - .decryptKeyBackupData(keyBackupData, + .decryptKeyBackupData( + keyBackupData, session.olmInboundGroupSession!!.sessionIdentifier(), cryptoTestData.roomId, - decryption!!) + decryption!! + ) assertNotNull(sessionData) // - Compare the decrypted megolm key with the original one keysBackupTestHelper.assertKeysEquals(session.exportKeys(), sessionData) @@ -290,13 +340,17 @@ class KeysBackupTest : InstrumentedTest { * - Restore must be successful */ @Test - @Ignore("This test will be ignored until it is fixed") fun restoreKeysBackupTest() { + val testHelper = CommonTestHelper(context()) + val cryptoTestHelper = CryptoTestHelper(testHelper) + val keysBackupTestHelper = KeysBackupTestHelper(testHelper, cryptoTestHelper) + val testData = keysBackupTestHelper.createKeysBackupScenarioWithPassword(null) // - Restore the e2e backup from the homeserver val importRoomKeysResult = testHelper.doSync { - testData.aliceSession2.cryptoService().keysBackupService().restoreKeysWithRecoveryKey(testData.aliceSession2.cryptoService().keysBackupService().keysBackupVersion!!, + testData.aliceSession2.cryptoService().keysBackupService().restoreKeysWithRecoveryKey( + testData.aliceSession2.cryptoService().keysBackupService().keysBackupVersion!!, testData.prepareKeysBackupDataResult.megolmBackupCreationInfo.recoveryKey, null, null, @@ -374,8 +428,11 @@ class KeysBackupTest : InstrumentedTest { * - It must be trusted and must have with 2 signatures now */ @Test - @Ignore("This test will be ignored until it is fixed") fun trustKeyBackupVersionTest() { + val testHelper = CommonTestHelper(context()) + val cryptoTestHelper = CryptoTestHelper(testHelper) + val keysBackupTestHelper = KeysBackupTestHelper(testHelper, cryptoTestHelper) + // - Do an e2e backup to the homeserver with a recovery key // - And log Alice on a new device val testData = keysBackupTestHelper.createKeysBackupScenarioWithPassword(null) @@ -434,8 +491,11 @@ class KeysBackupTest : InstrumentedTest { * - It must be trusted and must have with 2 signatures now */ @Test - @Ignore("This test will be ignored until it is fixed") fun trustKeyBackupVersionWithRecoveryKeyTest() { + val testHelper = CommonTestHelper(context()) + val cryptoTestHelper = CryptoTestHelper(testHelper) + val keysBackupTestHelper = KeysBackupTestHelper(testHelper, cryptoTestHelper) + // - Do an e2e backup to the homeserver with a recovery key // - And log Alice on a new device val testData = keysBackupTestHelper.createKeysBackupScenarioWithPassword(null) @@ -492,8 +552,11 @@ class KeysBackupTest : InstrumentedTest { * - The backup must still be untrusted and disabled */ @Test - @Ignore("This test will be ignored until it is fixed") fun trustKeyBackupVersionWithWrongRecoveryKeyTest() { + val testHelper = CommonTestHelper(context()) + val cryptoTestHelper = CryptoTestHelper(testHelper) + val keysBackupTestHelper = KeysBackupTestHelper(testHelper, cryptoTestHelper) + // - Do an e2e backup to the homeserver with a recovery key // - And log Alice on a new device val testData = keysBackupTestHelper.createKeysBackupScenarioWithPassword(null) @@ -534,8 +597,11 @@ class KeysBackupTest : InstrumentedTest { * - It must be trusted and must have with 2 signatures now */ @Test - @Ignore("This test will be ignored until it is fixed") fun trustKeyBackupVersionWithPasswordTest() { + val testHelper = CommonTestHelper(context()) + val cryptoTestHelper = CryptoTestHelper(testHelper) + val keysBackupTestHelper = KeysBackupTestHelper(testHelper, cryptoTestHelper) + val password = "Password" // - Do an e2e backup to the homeserver with a password @@ -594,8 +660,11 @@ class KeysBackupTest : InstrumentedTest { * - The backup must still be untrusted and disabled */ @Test - @Ignore("This test will be ignored until it is fixed") fun trustKeyBackupVersionWithWrongPasswordTest() { + val testHelper = CommonTestHelper(context()) + val cryptoTestHelper = CryptoTestHelper(testHelper) + val keysBackupTestHelper = KeysBackupTestHelper(testHelper, cryptoTestHelper) + val password = "Password" val badPassword = "Bad Password" @@ -635,8 +704,11 @@ class KeysBackupTest : InstrumentedTest { * - It must fail */ @Test - @Ignore("This test will be ignored until it is fixed") fun restoreKeysBackupWithAWrongRecoveryKeyTest() { + val testHelper = CommonTestHelper(context()) + val cryptoTestHelper = CryptoTestHelper(testHelper) + val keysBackupTestHelper = KeysBackupTestHelper(testHelper, cryptoTestHelper) + val testData = keysBackupTestHelper.createKeysBackupScenarioWithPassword(null) // - Try to restore the e2e backup with a wrong recovery key @@ -669,8 +741,11 @@ class KeysBackupTest : InstrumentedTest { * - Restore must be successful */ @Test - @Ignore("This test will be ignored until it is fixed") fun testBackupWithPassword() { + val testHelper = CommonTestHelper(context()) + val cryptoTestHelper = CryptoTestHelper(testHelper) + val keysBackupTestHelper = KeysBackupTestHelper(testHelper, cryptoTestHelper) + val password = "password" val testData = keysBackupTestHelper.createKeysBackupScenarioWithPassword(password) @@ -679,7 +754,8 @@ class KeysBackupTest : InstrumentedTest { val steps = ArrayList() val importRoomKeysResult = testHelper.doSync { - testData.aliceSession2.cryptoService().keysBackupService().restoreKeyBackupWithPassword(testData.aliceSession2.cryptoService().keysBackupService().keysBackupVersion!!, + testData.aliceSession2.cryptoService().keysBackupService().restoreKeyBackupWithPassword( + testData.aliceSession2.cryptoService().keysBackupService().keysBackupVersion!!, password, null, null, @@ -725,8 +801,11 @@ class KeysBackupTest : InstrumentedTest { * - It must fail */ @Test - @Ignore("This test will be ignored until it is fixed") fun restoreKeysBackupWithAWrongPasswordTest() { + val testHelper = CommonTestHelper(context()) + val cryptoTestHelper = CryptoTestHelper(testHelper) + val keysBackupTestHelper = KeysBackupTestHelper(testHelper, cryptoTestHelper) + val password = "password" val wrongPassword = "passw0rd" @@ -762,15 +841,19 @@ class KeysBackupTest : InstrumentedTest { * - Restore must be successful */ @Test - @Ignore("This test will be ignored until it is fixed") fun testUseRecoveryKeyToRestoreAPasswordBasedKeysBackup() { + val testHelper = CommonTestHelper(context()) + val cryptoTestHelper = CryptoTestHelper(testHelper) + val keysBackupTestHelper = KeysBackupTestHelper(testHelper, cryptoTestHelper) + val password = "password" val testData = keysBackupTestHelper.createKeysBackupScenarioWithPassword(password) // - Restore the e2e backup with the recovery key. val importRoomKeysResult = testHelper.doSync { - testData.aliceSession2.cryptoService().keysBackupService().restoreKeysWithRecoveryKey(testData.aliceSession2.cryptoService().keysBackupService().keysBackupVersion!!, + testData.aliceSession2.cryptoService().keysBackupService().restoreKeysWithRecoveryKey( + testData.aliceSession2.cryptoService().keysBackupService().keysBackupVersion!!, testData.prepareKeysBackupDataResult.megolmBackupCreationInfo.recoveryKey, null, null, @@ -791,8 +874,11 @@ class KeysBackupTest : InstrumentedTest { * - It must fail */ @Test - @Ignore("This test will be ignored until it is fixed") fun testUsePasswordToRestoreARecoveryKeyBasedKeysBackup() { + val testHelper = CommonTestHelper(context()) + val cryptoTestHelper = CryptoTestHelper(testHelper) + val keysBackupTestHelper = KeysBackupTestHelper(testHelper, cryptoTestHelper) + val testData = keysBackupTestHelper.createKeysBackupScenarioWithPassword(null) // - Try to restore the e2e backup with a password @@ -823,8 +909,11 @@ class KeysBackupTest : InstrumentedTest { * - Check the returned KeysVersionResult is trusted */ @Test - @Ignore("This test will be ignored until it is fixed") fun testIsKeysBackupTrusted() { + val testHelper = CommonTestHelper(context()) + val cryptoTestHelper = CryptoTestHelper(testHelper) + val keysBackupTestHelper = KeysBackupTestHelper(testHelper, cryptoTestHelper) + // - Create a backup version val cryptoTestData = cryptoTestHelper.doE2ETestWithAliceAndBobInARoomWithEncryptedMessages() @@ -849,7 +938,7 @@ class KeysBackupTest : InstrumentedTest { assertTrue(keysBackupVersionTrust.usable) assertEquals(1, keysBackupVersionTrust.signatures.size) - val signature = keysBackupVersionTrust.signatures[0] + val signature = keysBackupVersionTrust.signatures[0] as KeysBackupVersionTrustSignature.DeviceSignature assertTrue(signature.valid) assertNotNull(signature.device) assertEquals(cryptoTestData.firstSession.cryptoService().getMyDevice().deviceId, signature.deviceId) @@ -859,66 +948,6 @@ class KeysBackupTest : InstrumentedTest { cryptoTestData.cleanUp(testHelper) } - /** - * Check backup starts automatically if there is an existing and compatible backup - * version on the homeserver. - * - Create a backup version - * - Restart alice session - * -> The new alice session must back up to the same version - */ - @Test - @Ignore("This test will be ignored until it is fixed") - fun testCheckAndStartKeysBackupWhenRestartingAMatrixSession() { - // - Create a backup version - val cryptoTestData = cryptoTestHelper.doE2ETestWithAliceAndBobInARoomWithEncryptedMessages() - - val keysBackup = cryptoTestData.firstSession.cryptoService().keysBackupService() - - val stateObserver = StateObserver(keysBackup) - - assertFalse(keysBackup.isEnabled) - - val keyBackupCreationInfo = keysBackupTestHelper.prepareAndCreateKeysBackupData(keysBackup) - - assertTrue(keysBackup.isEnabled) - - // - Restart alice session - // - Log Alice on a new device - val aliceSession2 = testHelper.logIntoAccount(cryptoTestData.firstSession.myUserId, KeysBackupTestConstants.defaultSessionParamsWithInitialSync) - - cryptoTestData.cleanUp(testHelper) - - val keysBackup2 = aliceSession2.cryptoService().keysBackupService() - - val stateObserver2 = StateObserver(keysBackup2) - - // -> The new alice session must back up to the same version - val latch = CountDownLatch(1) - var count = 0 - keysBackup2.addListener(object : KeysBackupStateListener { - override fun onStateChange(newState: KeysBackupState) { - // Check the backup completes - if (newState == KeysBackupState.ReadyToBackUp) { - count++ - - if (count == 2) { - // Remove itself from the list of listeners - keysBackup2.removeListener(this) - - latch.countDown() - } - } - } - }) - testHelper.await(latch) - - assertEquals(keyBackupCreationInfo.version, keysBackup2.currentBackupVersion) - - stateObserver.stopAndCheckStates(null) - stateObserver2.stopAndCheckStates(null) - testHelper.signOutAndClose(aliceSession2) - } - /** * Check WrongBackUpVersion state * @@ -929,6 +958,10 @@ class KeysBackupTest : InstrumentedTest { */ @Test fun testBackupWhenAnotherBackupWasCreated() { + val testHelper = CommonTestHelper(context()) + val cryptoTestHelper = CryptoTestHelper(testHelper) + val keysBackupTestHelper = KeysBackupTestHelper(testHelper, cryptoTestHelper) + // - Create a backup version val cryptoTestData = cryptoTestHelper.doE2ETestWithAliceAndBobInARoomWithEncryptedMessages() @@ -999,8 +1032,11 @@ class KeysBackupTest : InstrumentedTest { * -> It must success */ @Test - @Ignore("This test will be ignored until it is fixed") fun testBackupAfterVerifyingADevice() { + val testHelper = CommonTestHelper(context()) + val cryptoTestHelper = CryptoTestHelper(testHelper) + val keysBackupTestHelper = KeysBackupTestHelper(testHelper, cryptoTestHelper) + // - Create a backup version val cryptoTestData = cryptoTestHelper.doE2ETestWithAliceAndBobInARoomWithEncryptedMessages() @@ -1033,6 +1069,8 @@ class KeysBackupTest : InstrumentedTest { // - Try to backup all in aliceSession2, it must fail val keysBackup2 = aliceSession2.cryptoService().keysBackupService() + assertFalse("Backup should not be enabled", keysBackup2.isEnabled) + val stateObserver2 = StateObserver(keysBackup2) var isSuccessful = false @@ -1050,11 +1088,15 @@ class KeysBackupTest : InstrumentedTest { assertFalse(isSuccessful) // Backup state must be NotTrusted - assertEquals(KeysBackupState.NotTrusted, keysBackup2.state) - assertFalse(keysBackup2.isEnabled) + assertEquals("Backup state must be NotTrusted", KeysBackupState.NotTrusted, keysBackup2.state) + assertFalse("Backup should not be enabled", keysBackup2.isEnabled) // - Validate the old device from the new one - aliceSession2.cryptoService().setDeviceVerification(DeviceTrustLevel(crossSigningVerified = false, locallyVerified = true), aliceSession2.myUserId, oldDeviceId) + aliceSession2.cryptoService().setDeviceVerification( + DeviceTrustLevel(crossSigningVerified = false, locallyVerified = true), + aliceSession2.myUserId, + oldDeviceId + ) // -> Backup should automatically enable on the new device val latch4 = CountDownLatch(1) @@ -1093,6 +1135,10 @@ class KeysBackupTest : InstrumentedTest { */ @Test fun deleteKeysBackupTest() { + val testHelper = CommonTestHelper(context()) + val cryptoTestHelper = CryptoTestHelper(testHelper) + val keysBackupTestHelper = KeysBackupTestHelper(testHelper, cryptoTestHelper) + // - Create a backup version val cryptoTestData = cryptoTestHelper.doE2ETestWithAliceAndBobInARoomWithEncryptedMessages() diff --git a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/keysbackup/KeysBackupTestHelper.kt b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/keysbackup/KeysBackupTestHelper.kt index ac83cb8882..2220536e28 100644 --- a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/keysbackup/KeysBackupTestHelper.kt +++ b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/keysbackup/KeysBackupTestHelper.kt @@ -88,10 +88,12 @@ internal class KeysBackupTestHelper( stateObserver.stopAndCheckStates(null) - return KeysBackupScenarioData(cryptoTestData, + return KeysBackupScenarioData( + cryptoTestData, aliceKeys, prepareKeysBackupDataResult, - aliceSession2) + aliceSession2 + ) } fun prepareAndCreateKeysBackupData(keysBackup: KeysBackupService, @@ -104,14 +106,14 @@ internal class KeysBackupTestHelper( Assert.assertNotNull(megolmBackupCreationInfo) - Assert.assertFalse(keysBackup.isEnabled) + Assert.assertFalse("Key backup should not be enabled before creation", keysBackup.isEnabled) // Create the version val keysVersion = testHelper.doSync { keysBackup.createKeysBackupVersion(megolmBackupCreationInfo, it) } - Assert.assertNotNull(keysVersion.version) + Assert.assertNotNull("Key backup version should not be null", keysVersion.version) // Backup must be enable now Assert.assertTrue(keysBackup.isEnabled) diff --git a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/ssss/QuadSTests.kt b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/ssss/QuadSTests.kt index d6baa4b1e0..c758050fc9 100644 --- a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/ssss/QuadSTests.kt +++ b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/ssss/QuadSTests.kt @@ -60,7 +60,7 @@ class QuadSTests : InstrumentedTest { val aliceSession = testHelper.createAccount(TestConstants.USER_ALICE, SessionTestParams(true)) - val quadS = aliceSession.sharedSecretStorageService + val quadS = aliceSession.sharedSecretStorageService() val TEST_KEY_ID = "my.test.Key" @@ -120,7 +120,7 @@ class QuadSTests : InstrumentedTest { // Store a secret val clearSecret = "42".toByteArray().toBase64NoPadding() testHelper.runBlockingTest { - aliceSession.sharedSecretStorageService.storeSecret( + aliceSession.sharedSecretStorageService().storeSecret( "secret.of.life", clearSecret, listOf(SharedSecretStorageService.KeyRef(null, keySpec)) // default key @@ -141,7 +141,7 @@ class QuadSTests : InstrumentedTest { // Try to decrypt?? val decryptedSecret = testHelper.runBlockingTest { - aliceSession.sharedSecretStorageService.getSecret( + aliceSession.sharedSecretStorageService().getSecret( "secret.of.life", null, // default key keySpec!! @@ -158,7 +158,7 @@ class QuadSTests : InstrumentedTest { val aliceSession = testHelper.createAccount(TestConstants.USER_ALICE, SessionTestParams(true)) - val quadS = aliceSession.sharedSecretStorageService + val quadS = aliceSession.sharedSecretStorageService() val TEST_KEY_ID = "my.test.Key" @@ -187,7 +187,7 @@ class QuadSTests : InstrumentedTest { val mySecretText = "Lorem ipsum dolor sit amet, consectetur adipiscing elit" testHelper.runBlockingTest { - aliceSession.sharedSecretStorageService.storeSecret( + aliceSession.sharedSecretStorageService().storeSecret( "my.secret", mySecretText.toByteArray().toBase64NoPadding(), listOf( @@ -207,14 +207,16 @@ class QuadSTests : InstrumentedTest { // Assert that can decrypt with both keys testHelper.runBlockingTest { - aliceSession.sharedSecretStorageService.getSecret("my.secret", + aliceSession.sharedSecretStorageService().getSecret( + "my.secret", keyId1, RawBytesKeySpec.fromRecoveryKey(key1Info.recoveryKey)!! ) } testHelper.runBlockingTest { - aliceSession.sharedSecretStorageService.getSecret("my.secret", + aliceSession.sharedSecretStorageService().getSecret( + "my.secret", keyId2, RawBytesKeySpec.fromRecoveryKey(key2Info.recoveryKey)!! ) @@ -236,7 +238,7 @@ class QuadSTests : InstrumentedTest { val mySecretText = "Lorem ipsum dolor sit amet, consectetur adipiscing elit" testHelper.runBlockingTest { - aliceSession.sharedSecretStorageService.storeSecret( + aliceSession.sharedSecretStorageService().storeSecret( "my.secret", mySecretText.toByteArray().toBase64NoPadding(), listOf(SharedSecretStorageService.KeyRef(keyId1, RawBytesKeySpec.fromRecoveryKey(key1Info.recoveryKey))) @@ -245,13 +247,15 @@ class QuadSTests : InstrumentedTest { testHelper.runBlockingTest { try { - aliceSession.sharedSecretStorageService.getSecret("my.secret", + aliceSession.sharedSecretStorageService().getSecret( + "my.secret", keyId1, RawBytesKeySpec.fromPassphrase( "A bad passphrase", key1Info.content?.passphrase?.salt ?: "", key1Info.content?.passphrase?.iterations ?: 0, - null) + null + ) ) } catch (throwable: Throwable) { assert(throwable is SharedSecretStorageError.BadMac) @@ -260,13 +264,15 @@ class QuadSTests : InstrumentedTest { // Now try with correct key testHelper.runBlockingTest { - aliceSession.sharedSecretStorageService.getSecret("my.secret", + aliceSession.sharedSecretStorageService().getSecret( + "my.secret", keyId1, RawBytesKeySpec.fromPassphrase( passphrase, key1Info.content?.passphrase?.salt ?: "", key1Info.content?.passphrase?.iterations ?: 0, - null) + null + ) ) } @@ -292,7 +298,7 @@ class QuadSTests : InstrumentedTest { } private fun generatedSecret(session: Session, keyId: String, asDefault: Boolean = true): SsssKeyCreationInfo { - val quadS = session.sharedSecretStorageService + val quadS = session.sharedSecretStorageService() val testHelper = CommonTestHelper(context()) val creationInfo = testHelper.runBlockingTest { @@ -312,7 +318,7 @@ class QuadSTests : InstrumentedTest { } private fun generatedSecretFromPassphrase(session: Session, passphrase: String, keyId: String, asDefault: Boolean = true): SsssKeyCreationInfo { - val quadS = session.sharedSecretStorageService + val quadS = session.sharedSecretStorageService() val testHelper = CommonTestHelper(context()) val creationInfo = testHelper.runBlockingTest { @@ -321,7 +327,8 @@ class QuadSTests : InstrumentedTest { keyId, passphrase, emptyKeySigner, - null) + null + ) } assertAccountData(session, "${DefaultSharedSecretStorageService.KEY_ID_BASE}.$keyId") diff --git a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/verification/SASTest.kt b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/verification/SASTest.kt index 14e659e2b6..2892cf8464 100644 --- a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/verification/SASTest.kt +++ b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/verification/SASTest.kt @@ -75,10 +75,12 @@ class SASTest : InstrumentedTest { } bobVerificationService.addListener(bobListener) - val txID = aliceVerificationService.beginKeyVerification(VerificationMethod.SAS, + val txID = aliceVerificationService.beginKeyVerification( + VerificationMethod.SAS, bobSession.myUserId, bobSession.cryptoService().getMyDevice().deviceId, - null) + null + ) assertNotNull("Alice should have a started transaction", txID) val aliceKeyTx = aliceVerificationService.getExistingTransaction(bobSession.myUserId, txID!!) @@ -467,8 +469,10 @@ class SASTest : InstrumentedTest { val aliceTx = aliceVerificationService.getExistingTransaction(bobUserId, verificationSAS!!) as SASDefaultVerificationTransaction val bobTx = bobVerificationService.getExistingTransaction(aliceSession.myUserId, verificationSAS) as SASDefaultVerificationTransaction - assertEquals("Should have same SAS", aliceTx.getShortCodeRepresentation(SasMode.DECIMAL), - bobTx.getShortCodeRepresentation(SasMode.DECIMAL)) + assertEquals( + "Should have same SAS", aliceTx.getShortCodeRepresentation(SasMode.DECIMAL), + bobTx.getShortCodeRepresentation(SasMode.DECIMAL) + ) cryptoTestData.cleanUp(testHelper) } @@ -544,7 +548,8 @@ class SASTest : InstrumentedTest { // Assert that devices are verified val bobDeviceInfoFromAlicePOV: CryptoDeviceInfo? = aliceSession.cryptoService().getDeviceInfo(bobUserId, bobDeviceId) - val aliceDeviceInfoFromBobPOV: CryptoDeviceInfo? = bobSession.cryptoService().getDeviceInfo(aliceSession.myUserId, aliceSession.cryptoService().getMyDevice().deviceId) + val aliceDeviceInfoFromBobPOV: CryptoDeviceInfo? = + bobSession.cryptoService().getDeviceInfo(aliceSession.myUserId, aliceSession.cryptoService().getMyDevice().deviceId) assertTrue("alice device should be verified from bob point of view", aliceDeviceInfoFromBobPOV!!.isVerified) assertTrue("bob device should be verified from alice point of view", bobDeviceInfoFromAlicePOV!!.isVerified) @@ -611,14 +616,16 @@ class SASTest : InstrumentedTest { requestID!!, cryptoTestData.roomId, bobSession.myUserId, - bobSession.sessionParams.deviceId!!) + bobSession.sessionParams.deviceId!! + ) bobVerificationService.beginKeyVerificationInDMs( VerificationMethod.SAS, requestID!!, cryptoTestData.roomId, aliceSession.myUserId, - aliceSession.sessionParams.deviceId!!) + aliceSession.sessionParams.deviceId!! + ) // we should reach SHOW SAS on both var alicePovTx: SasVerificationTransaction? diff --git a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/verification/qrcode/QrCodeTest.kt b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/verification/qrcode/QrCodeTest.kt index 76bf6dc040..d7b4d636fc 100644 --- a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/verification/qrcode/QrCodeTest.kt +++ b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/verification/qrcode/QrCodeTest.kt @@ -37,7 +37,8 @@ class QrCodeTest : InstrumentedTest { sharedSecret = "MTIzNDU2Nzg" ) - private val value1 = "MATRIX\u0002\u0000\u0000\u000DMaTransaction\u0092Ñ0qCú²íq\u0087á®\u0013à\u0098\u0091\u000DÇéoÃ\"_²lq]hC«¢UMynd¤Ù.ô\u0091XäÏ\u0094ê\u008B«\u009Døl\u000F¿+\u008CË\u0014¤®õÁ\u008BA¥12345678" + private val value1 = + "MATRIX\u0002\u0000\u0000\u000DMaTransaction\u0092Ñ0qCú²íq\u0087á®\u0013à\u0098\u0091\u000DÇéoÃ\"_²lq]hC«¢UMynd¤Ù.ô\u0091XäÏ\u0094ê\u008B«\u009Døl\u000F¿+\u008CË\u0014¤®õÁ\u008BA¥12345678" private val qrCode2 = QrCodeData.SelfVerifyingMasterKeyTrusted( transactionId = "MaTransaction", @@ -46,7 +47,8 @@ class QrCodeTest : InstrumentedTest { sharedSecret = "MTIzNDU2Nzg" ) - private val value2 = "MATRIX\u0002\u0001\u0000\u000DMaTransaction\u0092Ñ0qCú²íq\u0087á®\u0013à\u0098\u0091\u000DÇéoÃ\"_²lq]hC«¢UMynd¤Ù.ô\u0091XäÏ\u0094ê\u008B«\u009Døl\u000F¿+\u008CË\u0014¤®õÁ\u008BA¥12345678" + private val value2 = + "MATRIX\u0002\u0001\u0000\u000DMaTransaction\u0092Ñ0qCú²íq\u0087á®\u0013à\u0098\u0091\u000DÇéoÃ\"_²lq]hC«¢UMynd¤Ù.ô\u0091XäÏ\u0094ê\u008B«\u009Døl\u000F¿+\u008CË\u0014¤®õÁ\u008BA¥12345678" private val qrCode3 = QrCodeData.SelfVerifyingMasterKeyNotTrusted( transactionId = "MaTransaction", @@ -55,7 +57,8 @@ class QrCodeTest : InstrumentedTest { sharedSecret = "MTIzNDU2Nzg" ) - private val value3 = "MATRIX\u0002\u0002\u0000\u000DMaTransactionMynd¤Ù.ô\u0091XäÏ\u0094ê\u008B«\u009Døl\u000F¿+\u008CË\u0014¤®õÁ\u008BA¥\u0092Ñ0qCú²íq\u0087á®\u0013à\u0098\u0091\u000DÇéoÃ\"_²lq]hC«¢U12345678" + private val value3 = + "MATRIX\u0002\u0002\u0000\u000DMaTransactionMynd¤Ù.ô\u0091XäÏ\u0094ê\u008B«\u009Døl\u000F¿+\u008CË\u0014¤®õÁ\u008BA¥\u0092Ñ0qCú²íq\u0087á®\u0013à\u0098\u0091\u000DÇéoÃ\"_²lq]hC«¢U12345678" private val sharedSecretByteArray = "12345678".toByteArray(Charsets.ISO_8859_1) diff --git a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/verification/qrcode/VerificationTest.kt b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/verification/qrcode/VerificationTest.kt index 2c96568102..df3b2ffe27 100644 --- a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/verification/qrcode/VerificationTest.kt +++ b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/verification/qrcode/VerificationTest.kt @@ -19,7 +19,6 @@ package org.matrix.android.sdk.internal.crypto.verification.qrcode import androidx.test.ext.junit.runners.AndroidJUnit4 import org.amshove.kluent.shouldBe import org.junit.FixMethodOrder -import org.junit.Ignore import org.junit.Test import org.junit.runner.RunWith import org.junit.runners.MethodSorters @@ -40,7 +39,6 @@ import kotlin.coroutines.resume @RunWith(AndroidJUnit4::class) @FixMethodOrder(MethodSorters.JVM) -@Ignore("This test is flaky ; see issue #5449") class VerificationTest : InstrumentedTest { data class ExpectedResult( @@ -155,8 +153,8 @@ class VerificationTest : InstrumentedTest { bobSupportedMethods: List, expectedResultForAlice: ExpectedResult, expectedResultForBob: ExpectedResult) { - val testHelper = CommonTestHelper(context()) - val cryptoTestHelper = CryptoTestHelper(testHelper) + val testHelper = CommonTestHelper(context()) + val cryptoTestHelper = CryptoTestHelper(testHelper) val cryptoTestData = cryptoTestHelper.doE2ETestWithAliceAndBobInARoom() val aliceSession = cryptoTestData.firstSession @@ -175,7 +173,8 @@ class VerificationTest : InstrumentedTest { ) ) } - }, callback) + }, callback + ) } testHelper.doSync { callback -> @@ -191,7 +190,8 @@ class VerificationTest : InstrumentedTest { ) ) } - }, callback) + }, callback + ) } val aliceVerificationService = aliceSession.cryptoService().verificationService() diff --git a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/session/room/send/MarkdownParserTest.kt b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/session/room/send/MarkdownParserTest.kt index ef98ed22c7..acb23bf723 100644 --- a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/session/room/send/MarkdownParserTest.kt +++ b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/session/room/send/MarkdownParserTest.kt @@ -71,10 +71,12 @@ class MarkdownParserTest : InstrumentedTest { testIdentity("") testIdentity("a") testIdentity("1") - testIdentity("Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et " + - "dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea com" + - "modo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pari" + - "atur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.") + testIdentity( + "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et " + + "dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea com" + + "modo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pari" + + "atur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum." + ) } @Test @@ -294,16 +296,20 @@ class MarkdownParserTest : InstrumentedTest { "$markdownPattern$name$markdownPattern" .let { markdownParser.parse(it) - .expect(expectedText = it, - expectedFormattedText = "<$htmlExpectedTag>$name") + .expect( + expectedText = it, + expectedFormattedText = "<$htmlExpectedTag>$name" + ) } // Test twice the same tag "$markdownPattern$name$markdownPattern and $markdownPattern$name bis$markdownPattern" .let { markdownParser.parse(it) - .expect(expectedText = it, - expectedFormattedText = "<$htmlExpectedTag>$name and <$htmlExpectedTag>$name bis") + .expect( + expectedText = it, + expectedFormattedText = "<$htmlExpectedTag>$name and <$htmlExpectedTag>$name bis" + ) } val textBefore = "a" @@ -313,48 +319,60 @@ class MarkdownParserTest : InstrumentedTest { "$textBefore$markdownPattern$name$markdownPattern" .let { markdownParser.parse(it) - .expect(expectedText = it, - expectedFormattedText = "$textBefore<$htmlExpectedTag>$name") + .expect( + expectedText = it, + expectedFormattedText = "$textBefore<$htmlExpectedTag>$name" + ) } // With text before and space "$textBefore $markdownPattern$name$markdownPattern" .let { markdownParser.parse(it) - .expect(expectedText = it, - expectedFormattedText = "$textBefore <$htmlExpectedTag>$name") + .expect( + expectedText = it, + expectedFormattedText = "$textBefore <$htmlExpectedTag>$name" + ) } // With sticked text after "$markdownPattern$name$markdownPattern$textAfter" .let { markdownParser.parse(it) - .expect(expectedText = it, - expectedFormattedText = "<$htmlExpectedTag>$name$textAfter") + .expect( + expectedText = it, + expectedFormattedText = "<$htmlExpectedTag>$name$textAfter" + ) } // With space and text after "$markdownPattern$name$markdownPattern $textAfter" .let { markdownParser.parse(it) - .expect(expectedText = it, - expectedFormattedText = "<$htmlExpectedTag>$name $textAfter") + .expect( + expectedText = it, + expectedFormattedText = "<$htmlExpectedTag>$name $textAfter" + ) } // With sticked text before and text after "$textBefore$markdownPattern$name$markdownPattern$textAfter" .let { markdownParser.parse(it) - .expect(expectedText = it, - expectedFormattedText = "a<$htmlExpectedTag>$name$textAfter") + .expect( + expectedText = it, + expectedFormattedText = "a<$htmlExpectedTag>$name$textAfter" + ) } // With text before and after, with spaces "$textBefore $markdownPattern$name$markdownPattern $textAfter" .let { markdownParser.parse(it) - .expect(expectedText = it, - expectedFormattedText = "$textBefore <$htmlExpectedTag>$name $textAfter") + .expect( + expectedText = it, + expectedFormattedText = "$textBefore <$htmlExpectedTag>$name $textAfter" + ) } } @@ -366,16 +384,20 @@ class MarkdownParserTest : InstrumentedTest { "$markdownPattern$name\n$name$markdownPattern" .let { markdownParser.parse(it) - .expect(expectedText = it, - expectedFormattedText = "<$htmlExpectedTag>$name$softBreak$name") + .expect( + expectedText = it, + expectedFormattedText = "<$htmlExpectedTag>$name$softBreak$name" + ) } // With new line between two blocks "$markdownPattern$name$markdownPattern\n$markdownPattern$name$markdownPattern" .let { markdownParser.parse(it) - .expect(expectedText = it, - expectedFormattedText = "<$htmlExpectedTag>$name
<$htmlExpectedTag>$name") + .expect( + expectedText = it, + expectedFormattedText = "<$htmlExpectedTag>$name
<$htmlExpectedTag>$name" + ) } } diff --git a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/util/JsonCanonicalizerTest.kt b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/util/JsonCanonicalizerTest.kt index d38afc6b62..67eafea55d 100644 --- a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/util/JsonCanonicalizerTest.kt +++ b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/util/JsonCanonicalizerTest.kt @@ -39,27 +39,35 @@ internal class JsonCanonicalizerTest : InstrumentedTest { """{"a":["c":"b","d":"e"]}""", """{"a":["d":"b","c":"e"]}""" ).forEach { - assertEquals(it, - JsonCanonicalizer.canonicalize(it)) + assertEquals( + it, + JsonCanonicalizer.canonicalize(it) + ) } } @Test fun reorderTest() { - assertEquals("""{"a":true,"b":false}""", - JsonCanonicalizer.canonicalize("""{"b":false,"a":true}""")) + assertEquals( + """{"a":true,"b":false}""", + JsonCanonicalizer.canonicalize("""{"b":false,"a":true}""") + ) } @Test fun realSampleTest() { - assertEquals("""{"algorithms":["m.megolm.v1.aes-sha2","m.olm.v1.curve25519-aes-sha2"],"device_id":"VSCUNFSOUI","keys":{"curve25519:VSCUNFSOUI":"utyOjnhiQ73qNhi9HlN0OgWIowe5gthTS8r0r9TcJ3o","ed25519:VSCUNFSOUI":"qNhEt+Yggaajet0hX\/FjTRLfySgs65ldYyomm7PIx6U"},"user_id":"@benoitx:matrix.org"}""", - JsonCanonicalizer.canonicalize("""{"algorithms":["m.megolm.v1.aes-sha2","m.olm.v1.curve25519-aes-sha2"],"device_id":"VSCUNFSOUI","user_id":"@benoitx:matrix.org","keys":{"curve25519:VSCUNFSOUI":"utyOjnhiQ73qNhi9HlN0OgWIowe5gthTS8r0r9TcJ3o","ed25519:VSCUNFSOUI":"qNhEt+Yggaajet0hX/FjTRLfySgs65ldYyomm7PIx6U"}}""")) + assertEquals( + """{"algorithms":["m.megolm.v1.aes-sha2","m.olm.v1.curve25519-aes-sha2"],"device_id":"VSCUNFSOUI","keys":{"curve25519:VSCUNFSOUI":"utyOjnhiQ73qNhi9HlN0OgWIowe5gthTS8r0r9TcJ3o","ed25519:VSCUNFSOUI":"qNhEt+Yggaajet0hX\/FjTRLfySgs65ldYyomm7PIx6U"},"user_id":"@benoitx:matrix.org"}""", + JsonCanonicalizer.canonicalize("""{"algorithms":["m.megolm.v1.aes-sha2","m.olm.v1.curve25519-aes-sha2"],"device_id":"VSCUNFSOUI","user_id":"@benoitx:matrix.org","keys":{"curve25519:VSCUNFSOUI":"utyOjnhiQ73qNhi9HlN0OgWIowe5gthTS8r0r9TcJ3o","ed25519:VSCUNFSOUI":"qNhEt+Yggaajet0hX/FjTRLfySgs65ldYyomm7PIx6U"}}""") + ) } @Test fun doubleQuoteTest() { - assertEquals("{\"a\":\"\\\"\"}", - JsonCanonicalizer.canonicalize("{\"a\":\"\\\"\"}")) + assertEquals( + "{\"a\":\"\\\"\"}", + JsonCanonicalizer.canonicalize("{\"a\":\"\\\"\"}") + ) } /* ========================================================================================== @@ -68,38 +76,52 @@ internal class JsonCanonicalizerTest : InstrumentedTest { @Test fun matrixOrg001Test() { - assertEquals("""{}""", - JsonCanonicalizer.canonicalize("""{}""")) + assertEquals( + """{}""", + JsonCanonicalizer.canonicalize("""{}""") + ) } @Test fun matrixOrg002Test() { - assertEquals("""{"one":1,"two":"Two"}""", - JsonCanonicalizer.canonicalize("""{ + assertEquals( + """{"one":1,"two":"Two"}""", + JsonCanonicalizer.canonicalize( + """{ "one": 1, "two": "Two" -}""")) +}""" + ) + ) } @Test fun matrixOrg003Test() { - assertEquals("""{"a":"1","b":"2"}""", - JsonCanonicalizer.canonicalize("""{ + assertEquals( + """{"a":"1","b":"2"}""", + JsonCanonicalizer.canonicalize( + """{ "b": "2", "a": "1" -}""")) +}""" + ) + ) } @Test fun matrixOrg004Test() { - assertEquals("""{"a":"1","b":"2"}""", - JsonCanonicalizer.canonicalize("""{"b":"2","a":"1"}""")) + assertEquals( + """{"a":"1","b":"2"}""", + JsonCanonicalizer.canonicalize("""{"b":"2","a":"1"}""") + ) } @Test fun matrixOrg005Test() { - assertEquals("""{"auth":{"mxid":"@john.doe:example.com","profile":{"display_name":"John Doe","three_pids":[{"address":"john.doe@example.org","medium":"email"},{"address":"123456789","medium":"msisdn"}]},"success":true}}""", - JsonCanonicalizer.canonicalize("""{ + assertEquals( + """{"auth":{"mxid":"@john.doe:example.com","profile":{"display_name":"John Doe","three_pids":[{"address":"john.doe@example.org","medium":"email"},{"address":"123456789","medium":"msisdn"}]},"success":true}}""", + JsonCanonicalizer.canonicalize( + """{ "auth": { "success": true, "mxid": "@john.doe:example.com", @@ -117,37 +139,53 @@ internal class JsonCanonicalizerTest : InstrumentedTest { ] } } -}""")) +}""" + ) + ) } @Test fun matrixOrg006Test() { - assertEquals("""{"a":"日本語"}""", - JsonCanonicalizer.canonicalize("""{ + assertEquals( + """{"a":"日本語"}""", + JsonCanonicalizer.canonicalize( + """{ "a": "日本語" -}""")) +}""" + ) + ) } @Test fun matrixOrg007Test() { - assertEquals("""{"日":1,"本":2}""", - JsonCanonicalizer.canonicalize("""{ + assertEquals( + """{"日":1,"本":2}""", + JsonCanonicalizer.canonicalize( + """{ "本": 2, "日": 1 -}""")) +}""" + ) + ) } @Test fun matrixOrg008Test() { - assertEquals("""{"a":"日"}""", - JsonCanonicalizer.canonicalize("{\"a\": \"\u65E5\"}")) + assertEquals( + """{"a":"日"}""", + JsonCanonicalizer.canonicalize("{\"a\": \"\u65E5\"}") + ) } @Test fun matrixOrg009Test() { - assertEquals("""{"a":null}""", - JsonCanonicalizer.canonicalize("""{ + assertEquals( + """{"a":null}""", + JsonCanonicalizer.canonicalize( + """{ "a": null -}""")) +}""" + ) + ) } } diff --git a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/ordering/StringOrderTest.kt b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/ordering/StringOrderTest.kt index 728986441a..b5870ebf69 100644 --- a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/ordering/StringOrderTest.kt +++ b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/ordering/StringOrderTest.kt @@ -26,9 +26,18 @@ class StringOrderTest { @Test fun testbasing() { - assertEquals("a", StringOrderUtils.baseToString(StringOrderUtils.stringToBase("a", StringOrderUtils.DEFAULT_ALPHABET), StringOrderUtils.DEFAULT_ALPHABET)) - assertEquals("element", StringOrderUtils.baseToString(StringOrderUtils.stringToBase("element", StringOrderUtils.DEFAULT_ALPHABET), StringOrderUtils.DEFAULT_ALPHABET)) - assertEquals("matrix", StringOrderUtils.baseToString(StringOrderUtils.stringToBase("matrix", StringOrderUtils.DEFAULT_ALPHABET), StringOrderUtils.DEFAULT_ALPHABET)) + assertEquals( + "a", + StringOrderUtils.baseToString(StringOrderUtils.stringToBase("a", StringOrderUtils.DEFAULT_ALPHABET), StringOrderUtils.DEFAULT_ALPHABET) + ) + assertEquals( + "element", + StringOrderUtils.baseToString(StringOrderUtils.stringToBase("element", StringOrderUtils.DEFAULT_ALPHABET), StringOrderUtils.DEFAULT_ALPHABET) + ) + assertEquals( + "matrix", + StringOrderUtils.baseToString(StringOrderUtils.stringToBase("matrix", StringOrderUtils.DEFAULT_ALPHABET), StringOrderUtils.DEFAULT_ALPHABET) + ) } @Test diff --git a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/session/room/threads/ThreadMessagingTest.kt b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/session/room/threads/ThreadMessagingTest.kt index dcb181f0c1..a2984dd27e 100644 --- a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/session/room/threads/ThreadMessagingTest.kt +++ b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/session/room/threads/ThreadMessagingTest.kt @@ -31,6 +31,7 @@ import org.matrix.android.sdk.InstrumentedTest import org.matrix.android.sdk.api.session.events.model.getRootThreadEventId import org.matrix.android.sdk.api.session.events.model.isTextMessage import org.matrix.android.sdk.api.session.events.model.isThread +import org.matrix.android.sdk.api.session.getRoom import org.matrix.android.sdk.api.session.room.timeline.Timeline import org.matrix.android.sdk.api.session.room.timeline.TimelineSettings import org.matrix.android.sdk.common.CommonTestHelper @@ -58,7 +59,8 @@ class ThreadMessagingTest : InstrumentedTest { val sentMessages = commonTestHelper.sendTextMessage( room = aliceRoom, message = textMessage, - nbOfMessages = 1) + nbOfMessages = 1 + ) val initMessage = sentMessages.first() @@ -72,7 +74,8 @@ class ThreadMessagingTest : InstrumentedTest { room = aliceRoom, message = "Reply In the above thread", numberOfMessages = 1, - rootThreadEventId = initMessage.root.eventId.orEmpty()) + rootThreadEventId = initMessage.root.eventId.orEmpty() + ) val replyInThread = repliesInThread.first() replyInThread.root.isThread().shouldBeTrue() @@ -80,7 +83,7 @@ class ThreadMessagingTest : InstrumentedTest { replyInThread.root.getRootThreadEventId().shouldBeEqualTo(initMessage.root.eventId) // The init normal message should now be a root thread event - val timeline = aliceRoom.createTimeline(null, TimelineSettings(30)) + val timeline = aliceRoom.timelineService().createTimeline(null, TimelineSettings(30)) timeline.start() aliceSession.startSync(true) @@ -115,7 +118,8 @@ class ThreadMessagingTest : InstrumentedTest { val sentMessages = commonTestHelper.sendTextMessage( room = aliceRoom, message = textMessage, - nbOfMessages = 1) + nbOfMessages = 1 + ) val initMessage = sentMessages.first() @@ -133,7 +137,8 @@ class ThreadMessagingTest : InstrumentedTest { room = bobRoom, message = "Reply In the above thread", numberOfMessages = 1, - rootThreadEventId = initMessage.root.eventId.orEmpty()) + rootThreadEventId = initMessage.root.eventId.orEmpty() + ) val replyInThread = repliesInThread.first() replyInThread.root.isThread().shouldBeTrue() @@ -141,7 +146,7 @@ class ThreadMessagingTest : InstrumentedTest { replyInThread.root.getRootThreadEventId().shouldBeEqualTo(initMessage.root.eventId) // The init normal message should now be a root thread event - val timeline = aliceRoom.createTimeline(null, TimelineSettings(30)) + val timeline = aliceRoom.timelineService().createTimeline(null, TimelineSettings(30)) timeline.start() aliceSession.startSync(true) @@ -189,7 +194,8 @@ class ThreadMessagingTest : InstrumentedTest { val sentMessages = commonTestHelper.sendTextMessage( room = aliceRoom, message = textMessage, - nbOfMessages = 5) + nbOfMessages = 5 + ) sentMessages.forEach { it.root.isThread().shouldBeFalse() @@ -205,7 +211,8 @@ class ThreadMessagingTest : InstrumentedTest { room = aliceRoom, message = "Reply In the above thread", numberOfMessages = 40, - rootThreadEventId = selectedInitMessage.root.eventId.orEmpty()) + rootThreadEventId = selectedInitMessage.root.eventId.orEmpty() + ) repliesInThread.forEach { it.root.isThread().shouldBeTrue() @@ -214,7 +221,7 @@ class ThreadMessagingTest : InstrumentedTest { } // The init normal message should now be a root thread event - val timeline = aliceRoom.createTimeline(null, TimelineSettings(30)) + val timeline = aliceRoom.timelineService().createTimeline(null, TimelineSettings(30)) timeline.start() aliceSession.startSync(true) @@ -252,7 +259,8 @@ class ThreadMessagingTest : InstrumentedTest { val sentMessages = commonTestHelper.sendTextMessage( room = aliceRoom, message = textMessage, - nbOfMessages = 5) + nbOfMessages = 5 + ) sentMessages.forEach { it.root.isThread().shouldBeFalse() @@ -269,7 +277,8 @@ class ThreadMessagingTest : InstrumentedTest { room = aliceRoom, message = "Alice reply In the above second thread message", numberOfMessages = 35, - rootThreadEventId = secondMessage.root.eventId.orEmpty()) + rootThreadEventId = secondMessage.root.eventId.orEmpty() + ) // Let's reply in timeline to that message from another user val bobSession = cryptoTestData.secondSession!! @@ -281,14 +290,16 @@ class ThreadMessagingTest : InstrumentedTest { room = bobRoom, message = "Bob reply In the above first thread message", numberOfMessages = 42, - rootThreadEventId = firstMessage.root.eventId.orEmpty()) + rootThreadEventId = firstMessage.root.eventId.orEmpty() + ) // Bob will also reply in second thread 5 times val bobThreadRepliesInSecondMessage = commonTestHelper.replyInThreadMessage( room = bobRoom, message = "Another Bob reply In the above second thread message", numberOfMessages = 20, - rootThreadEventId = secondMessage.root.eventId.orEmpty()) + rootThreadEventId = secondMessage.root.eventId.orEmpty() + ) aliceThreadRepliesInSecondMessage.forEach { it.root.isThread().shouldBeTrue() @@ -309,7 +320,7 @@ class ThreadMessagingTest : InstrumentedTest { } // The init normal message should now be a root thread event - val timeline = aliceRoom.createTimeline(null, TimelineSettings(30)) + val timeline = aliceRoom.timelineService().createTimeline(null, TimelineSettings(30)) timeline.start() aliceSession.startSync(true) diff --git a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/session/room/timeline/ChunkEntityTest.kt b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/session/room/timeline/ChunkEntityTest.kt index 5c011c8b2f..94b2ba55a3 100644 --- a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/session/room/timeline/ChunkEntityTest.kt +++ b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/session/room/timeline/ChunkEntityTest.kt @@ -35,6 +35,7 @@ import org.matrix.android.sdk.internal.database.mapper.toEntity import org.matrix.android.sdk.internal.database.model.ChunkEntity import org.matrix.android.sdk.internal.database.model.SessionRealmModule import org.matrix.android.sdk.internal.session.room.timeline.PaginationDirection +import org.matrix.android.sdk.internal.util.time.DefaultClock import org.matrix.android.sdk.session.room.timeline.RoomDataHelper.createFakeListOfEvents import org.matrix.android.sdk.session.room.timeline.RoomDataHelper.createFakeMessageEvent @@ -42,6 +43,7 @@ import org.matrix.android.sdk.session.room.timeline.RoomDataHelper.createFakeMes internal class ChunkEntityTest : InstrumentedTest { private lateinit var monarchy: Monarchy + private val clock = DefaultClock() @Before fun setup() { @@ -59,14 +61,15 @@ internal class ChunkEntityTest : InstrumentedTest { monarchy.runTransactionSync { realm -> val chunk: ChunkEntity = realm.createObject() - val fakeEvent = createFakeMessageEvent().toEntity(ROOM_ID, SendState.SYNCED, System.currentTimeMillis()).let { + val fakeEvent = createFakeMessageEvent().toEntity(ROOM_ID, SendState.SYNCED, clock.epochMillis()).let { realm.copyToRealm(it) } chunk.addTimelineEvent( roomId = ROOM_ID, eventEntity = fakeEvent, direction = PaginationDirection.FORWARDS, - roomMemberContentsByUser = emptyMap()) + roomMemberContentsByUser = emptyMap() + ) chunk.timelineEvents.size shouldBeEqualTo 1 } } @@ -75,19 +78,21 @@ internal class ChunkEntityTest : InstrumentedTest { fun add_shouldNotAdd_whenAlreadyIncluded() { monarchy.runTransactionSync { realm -> val chunk: ChunkEntity = realm.createObject() - val fakeEvent = createFakeMessageEvent().toEntity(ROOM_ID, SendState.SYNCED, System.currentTimeMillis()).let { + val fakeEvent = createFakeMessageEvent().toEntity(ROOM_ID, SendState.SYNCED, clock.epochMillis()).let { realm.copyToRealm(it) } chunk.addTimelineEvent( roomId = ROOM_ID, eventEntity = fakeEvent, direction = PaginationDirection.FORWARDS, - roomMemberContentsByUser = emptyMap()) + roomMemberContentsByUser = emptyMap() + ) chunk.addTimelineEvent( roomId = ROOM_ID, eventEntity = fakeEvent, direction = PaginationDirection.FORWARDS, - roomMemberContentsByUser = emptyMap()) + roomMemberContentsByUser = emptyMap() + ) chunk.timelineEvents.size shouldBeEqualTo 1 } } @@ -153,14 +158,15 @@ internal class ChunkEntityTest : InstrumentedTest { events: List, direction: PaginationDirection) { events.forEach { event -> - val fakeEvent = event.toEntity(roomId, SendState.SYNCED, System.currentTimeMillis()).let { + val fakeEvent = event.toEntity(roomId, SendState.SYNCED, clock.epochMillis()).let { realm.copyToRealm(it) } addTimelineEvent( roomId = roomId, eventEntity = fakeEvent, direction = direction, - roomMemberContentsByUser = emptyMap()) + roomMemberContentsByUser = emptyMap() + ) } } diff --git a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/session/room/timeline/FakeGetContextOfEventTask.kt b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/session/room/timeline/FakeGetContextOfEventTask.kt index b86c86c0c7..ccf1c7c2c9 100644 --- a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/session/room/timeline/FakeGetContextOfEventTask.kt +++ b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/session/room/timeline/FakeGetContextOfEventTask.kt @@ -26,8 +26,8 @@ internal class FakeGetContextOfEventTask constructor(private val tokenChunkEvent override suspend fun execute(params: GetContextOfEventTask.Params): TokenChunkEventPersistor.Result { val fakeEvents = RoomDataHelper.createFakeListOfEvents(30) val tokenChunkEvent = FakeTokenChunkEvent( - Random.nextLong(System.currentTimeMillis()).toString(), - Random.nextLong(System.currentTimeMillis()).toString(), + Random.nextLong().toString(), + Random.nextLong().toString(), fakeEvents ) return tokenChunkEventPersistor.insertInDb(tokenChunkEvent, params.roomId, PaginationDirection.BACKWARDS) diff --git a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/session/room/timeline/FakePaginationTask.kt b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/session/room/timeline/FakePaginationTask.kt index d09bfb18c6..f241be0c5c 100644 --- a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/session/room/timeline/FakePaginationTask.kt +++ b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/session/room/timeline/FakePaginationTask.kt @@ -25,7 +25,7 @@ internal class FakePaginationTask @Inject constructor(private val tokenChunkEven override suspend fun execute(params: PaginationTask.Params): TokenChunkEventPersistor.Result { val fakeEvents = RoomDataHelper.createFakeListOfEvents(30) - val tokenChunkEvent = FakeTokenChunkEvent(params.from, Random.nextLong(System.currentTimeMillis()).toString(), fakeEvents) + val tokenChunkEvent = FakeTokenChunkEvent(params.from, Random.nextLong().toString(), fakeEvents) return tokenChunkEventPersistor.insertInDb(tokenChunkEvent, params.roomId, params.direction) } } diff --git a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/session/room/timeline/PollAggregationTest.kt b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/session/room/timeline/PollAggregationTest.kt index 6792d6ddfd..61ab6d4b40 100644 --- a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/session/room/timeline/PollAggregationTest.kt +++ b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/session/room/timeline/PollAggregationTest.kt @@ -30,6 +30,7 @@ import org.junit.runners.MethodSorters import org.matrix.android.sdk.InstrumentedTest import org.matrix.android.sdk.api.session.events.model.EventType import org.matrix.android.sdk.api.session.events.model.toModel +import org.matrix.android.sdk.api.session.getRoom import org.matrix.android.sdk.api.session.room.model.PollResponseAggregatedSummary import org.matrix.android.sdk.api.session.room.model.PollSummaryContent import org.matrix.android.sdk.api.session.room.model.message.MessagePollContent @@ -57,10 +58,10 @@ class PollAggregationTest : InstrumentedTest { val roomFromBobPOV = cryptoTestData.secondSession!!.getRoom(cryptoTestData.roomId)!! // Bob creates a poll - roomFromBobPOV.sendPoll(PollType.DISCLOSED, pollQuestion, pollOptions) + roomFromBobPOV.sendService().sendPoll(PollType.DISCLOSED, pollQuestion, pollOptions) aliceSession.startSync(true) - val aliceTimeline = roomFromAlicePOV.createTimeline(null, TimelineSettings(30)) + val aliceTimeline = roomFromAlicePOV.timelineService().createTimeline(null, TimelineSettings(30)) aliceTimeline.start() val TOTAL_TEST_COUNT = 7 @@ -83,37 +84,37 @@ class PollAggregationTest : InstrumentedTest { // Poll has just been created. testInitialPollConditions(pollContent, pollSummary) lock.countDown() - roomFromBobPOV.voteToPoll(pollEventId, pollContent.getBestPollCreationInfo()?.answers?.firstOrNull()?.id ?: "") + roomFromBobPOV.sendService().voteToPoll(pollEventId, pollContent.getBestPollCreationInfo()?.answers?.firstOrNull()?.id ?: "") } TOTAL_TEST_COUNT - 1 -> { // Bob: Option 1 testBobVotesOption1(pollContent, pollSummary) lock.countDown() - roomFromBobPOV.voteToPoll(pollEventId, pollContent.getBestPollCreationInfo()?.answers?.get(1)?.id ?: "") + roomFromBobPOV.sendService().voteToPoll(pollEventId, pollContent.getBestPollCreationInfo()?.answers?.get(1)?.id ?: "") } TOTAL_TEST_COUNT - 2 -> { // Bob: Option 2 testBobChangesVoteToOption2(pollContent, pollSummary) lock.countDown() - roomFromAlicePOV.voteToPoll(pollEventId, pollContent.getBestPollCreationInfo()?.answers?.get(1)?.id ?: "") + roomFromAlicePOV.sendService().voteToPoll(pollEventId, pollContent.getBestPollCreationInfo()?.answers?.get(1)?.id ?: "") } TOTAL_TEST_COUNT - 3 -> { // Alice: Option 2, Bob: Option 2 testAliceAndBobVoteToOption2(pollContent, pollSummary) lock.countDown() - roomFromAlicePOV.voteToPoll(pollEventId, pollContent.getBestPollCreationInfo()?.answers?.firstOrNull()?.id ?: "") + roomFromAlicePOV.sendService().voteToPoll(pollEventId, pollContent.getBestPollCreationInfo()?.answers?.firstOrNull()?.id ?: "") } TOTAL_TEST_COUNT - 4 -> { // Alice: Option 1, Bob: Option 2 testAliceVotesOption1AndBobVotesOption2(pollContent, pollSummary) lock.countDown() - roomFromBobPOV.endPoll(pollEventId) + roomFromBobPOV.sendService().endPoll(pollEventId) } TOTAL_TEST_COUNT - 5 -> { // Alice: Option 1, Bob: Option 2 [poll is ended] testEndedPoll(pollSummary) lock.countDown() - roomFromAlicePOV.voteToPoll(pollEventId, pollContent.getBestPollCreationInfo()?.answers?.get(1)?.id ?: "") + roomFromAlicePOV.sendService().voteToPoll(pollEventId, pollContent.getBestPollCreationInfo()?.answers?.get(1)?.id ?: "") } TOTAL_TEST_COUNT - 6 -> { // Alice: Option 1 (ignore change), Bob: Option 2 [poll is ended] diff --git a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/session/room/timeline/TimelineForwardPaginationTest.kt b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/session/room/timeline/TimelineForwardPaginationTest.kt index ee44af58b3..d5b4a07fc0 100644 --- a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/session/room/timeline/TimelineForwardPaginationTest.kt +++ b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/session/room/timeline/TimelineForwardPaginationTest.kt @@ -30,6 +30,7 @@ import org.matrix.android.sdk.InstrumentedTest import org.matrix.android.sdk.api.extensions.orFalse import org.matrix.android.sdk.api.session.events.model.EventType import org.matrix.android.sdk.api.session.events.model.toModel +import org.matrix.android.sdk.api.session.getRoom import org.matrix.android.sdk.api.session.room.model.message.MessageContent import org.matrix.android.sdk.api.session.room.timeline.Timeline import org.matrix.android.sdk.api.session.room.timeline.TimelineSettings @@ -70,11 +71,12 @@ class TimelineForwardPaginationTest : InstrumentedTest { val sentMessages = commonTestHelper.sendTextMessage( roomFromAlicePOV, message, - numberOfMessagesToSend) + numberOfMessagesToSend + ) // Alice clear the cache and restart the sync commonTestHelper.clearCacheAndSync(aliceSession) - val aliceTimeline = roomFromAlicePOV.createTimeline(null, TimelineSettings(30)) + val aliceTimeline = roomFromAlicePOV.timelineService().createTimeline(null, TimelineSettings(30)) aliceTimeline.start() // Alice sees the 10 last message of the room, and can only navigate BACKWARD diff --git a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/session/room/timeline/TimelinePreviousLastForwardTest.kt b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/session/room/timeline/TimelinePreviousLastForwardTest.kt index c6d40bcaa2..6e5fed8df9 100644 --- a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/session/room/timeline/TimelinePreviousLastForwardTest.kt +++ b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/session/room/timeline/TimelinePreviousLastForwardTest.kt @@ -28,6 +28,7 @@ import org.matrix.android.sdk.InstrumentedTest import org.matrix.android.sdk.api.extensions.orFalse import org.matrix.android.sdk.api.session.events.model.EventType import org.matrix.android.sdk.api.session.events.model.toModel +import org.matrix.android.sdk.api.session.getRoom import org.matrix.android.sdk.api.session.room.model.message.MessageContent import org.matrix.android.sdk.api.session.room.timeline.Timeline import org.matrix.android.sdk.api.session.room.timeline.TimelineSettings @@ -62,7 +63,7 @@ class TimelinePreviousLastForwardTest : InstrumentedTest { val roomFromAlicePOV = aliceSession.getRoom(aliceRoomId)!! val roomFromBobPOV = bobSession.getRoom(aliceRoomId)!! - val bobTimeline = roomFromBobPOV.createTimeline(null, TimelineSettings(30)) + val bobTimeline = roomFromBobPOV.timelineService().createTimeline(null, TimelineSettings(30)) bobTimeline.start() run { @@ -93,7 +94,8 @@ class TimelinePreviousLastForwardTest : InstrumentedTest { val firstMessageFromAliceId = commonTestHelper.sendTextMessage( roomFromAlicePOV, firstMessage, - 30) + 30 + ) .last() .eventId @@ -129,7 +131,8 @@ class TimelinePreviousLastForwardTest : InstrumentedTest { commonTestHelper.sendTextMessage( roomFromAlicePOV, secondMessage, - 30) + 30 + ) // Bob start to sync bobSession.startSync(true) diff --git a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/session/room/timeline/TimelineSimpleBackPaginationTest.kt b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/session/room/timeline/TimelineSimpleBackPaginationTest.kt index 53f76f1c46..42f710d7cf 100644 --- a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/session/room/timeline/TimelineSimpleBackPaginationTest.kt +++ b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/session/room/timeline/TimelineSimpleBackPaginationTest.kt @@ -28,6 +28,7 @@ import org.matrix.android.sdk.InstrumentedTest import org.matrix.android.sdk.api.extensions.orFalse import org.matrix.android.sdk.api.session.events.model.isTextMessage import org.matrix.android.sdk.api.session.events.model.toModel +import org.matrix.android.sdk.api.session.getRoom import org.matrix.android.sdk.api.session.room.model.message.MessageTextContent import org.matrix.android.sdk.api.session.room.timeline.Timeline import org.matrix.android.sdk.api.session.room.timeline.TimelineSettings @@ -63,9 +64,10 @@ class TimelineSimpleBackPaginationTest : InstrumentedTest { commonTestHelper.sendTextMessage( roomFromAlicePOV, message, - numberOfMessagesToSent) + numberOfMessagesToSent + ) - val bobTimeline = roomFromBobPOV.createTimeline(null, TimelineSettings(30)) + val bobTimeline = roomFromBobPOV.timelineService().createTimeline(null, TimelineSettings(30)) bobTimeline.start() commonTestHelper.waitWithLatch(timeout = TestConstants.timeOutMillis * 10) { diff --git a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/session/room/timeline/TimelineWithManyMembersTest.kt b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/session/room/timeline/TimelineWithManyMembersTest.kt index ce02b2b527..02430dda74 100644 --- a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/session/room/timeline/TimelineWithManyMembersTest.kt +++ b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/session/room/timeline/TimelineWithManyMembersTest.kt @@ -27,6 +27,7 @@ import org.junit.runners.MethodSorters import org.matrix.android.sdk.InstrumentedTest import org.matrix.android.sdk.api.extensions.orFalse import org.matrix.android.sdk.api.session.events.model.toModel +import org.matrix.android.sdk.api.session.getRoom import org.matrix.android.sdk.api.session.room.model.message.MessageContent import org.matrix.android.sdk.api.session.room.timeline.TimelineSettings import org.matrix.android.sdk.common.CommonTestHelper @@ -71,7 +72,7 @@ class TimelineWithManyMembersTest : InstrumentedTest { for (index in 1 until cryptoTestData.sessions.size) { val session = cryptoTestData.sessions[index] val roomForCurrentMember = session.getRoom(cryptoTestData.roomId)!! - val timelineForCurrentMember = roomForCurrentMember.createTimeline(null, TimelineSettings(30)) + val timelineForCurrentMember = roomForCurrentMember.timelineService().createTimeline(null, TimelineSettings(30)) timelineForCurrentMember.start() session.startSync(true) diff --git a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/session/search/SearchMessagesTest.kt b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/session/search/SearchMessagesTest.kt index fa07cf5a02..e17b7efbd6 100644 --- a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/session/search/SearchMessagesTest.kt +++ b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/session/search/SearchMessagesTest.kt @@ -24,6 +24,7 @@ import org.junit.runners.JUnit4 import org.junit.runners.MethodSorters import org.matrix.android.sdk.InstrumentedTest import org.matrix.android.sdk.api.extensions.orFalse +import org.matrix.android.sdk.api.session.getRoom import org.matrix.android.sdk.api.session.search.SearchResult import org.matrix.android.sdk.common.CommonTestHelper import org.matrix.android.sdk.common.CryptoTestData @@ -59,9 +60,10 @@ class SearchMessagesTest : InstrumentedTest { fun sendTextMessageAndSearchPartOfItUsingRoom() { doTest { cryptoTestData -> cryptoTestData.firstSession - .getRoom(cryptoTestData.roomId)!! + .searchService() .search( searchTerm = "lore", + roomId = cryptoTestData.roomId, limit = 10, includeProfile = true, afterLimit = 0, @@ -83,7 +85,8 @@ class SearchMessagesTest : InstrumentedTest { commonTestHelper.sendTextMessage( roomFromAlicePOV, MESSAGE, - 2) + 2 + ) val data = commonTestHelper.runBlockingTest { block.invoke(cryptoTestData) diff --git a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/session/space/SpaceCreationTest.kt b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/session/space/SpaceCreationTest.kt index 3b0f7586cc..b9760c1bfc 100644 --- a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/session/space/SpaceCreationTest.kt +++ b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/session/space/SpaceCreationTest.kt @@ -29,6 +29,7 @@ import org.junit.runners.MethodSorters import org.matrix.android.sdk.InstrumentedTest import org.matrix.android.sdk.api.session.events.model.EventType import org.matrix.android.sdk.api.session.events.model.toModel +import org.matrix.android.sdk.api.session.room.getStateEvent import org.matrix.android.sdk.api.session.room.model.GuestAccess import org.matrix.android.sdk.api.session.room.model.PowerLevelsContent import org.matrix.android.sdk.api.session.room.model.RoomGuestAccessContent @@ -147,7 +148,7 @@ class SpaceCreationTest : InstrumentedTest { // create a room var firstChild: String? = null commonTestHelper.waitWithLatch { - firstChild = aliceSession.createRoom(CreateRoomParams().apply { + firstChild = aliceSession.roomService().createRoom(CreateRoomParams().apply { this.name = "FirstRoom" this.topic = "Description of first room" this.preset = CreateRoomPreset.PRESET_PUBLIC_CHAT @@ -162,7 +163,7 @@ class SpaceCreationTest : InstrumentedTest { var secondChild: String? = null commonTestHelper.waitWithLatch { - secondChild = aliceSession.createRoom(CreateRoomParams().apply { + secondChild = aliceSession.roomService().createRoom(CreateRoomParams().apply { this.name = "SecondRoom" this.topic = "Description of second room" this.preset = CreateRoomPreset.PRESET_PUBLIC_CHAT diff --git a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/session/space/SpaceHierarchyTest.kt b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/session/space/SpaceHierarchyTest.kt index 20faa81bb6..6a17cb74ad 100644 --- a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/session/space/SpaceHierarchyTest.kt +++ b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/session/space/SpaceHierarchyTest.kt @@ -35,6 +35,9 @@ import org.matrix.android.sdk.api.session.Session import org.matrix.android.sdk.api.session.events.model.EventType import org.matrix.android.sdk.api.session.events.model.toContent import org.matrix.android.sdk.api.session.events.model.toModel +import org.matrix.android.sdk.api.session.getRoom +import org.matrix.android.sdk.api.session.getRoomSummary +import org.matrix.android.sdk.api.session.room.getStateEvent import org.matrix.android.sdk.api.session.room.model.PowerLevelsContent import org.matrix.android.sdk.api.session.room.model.RoomJoinRulesAllowEntry import org.matrix.android.sdk.api.session.room.model.RoomSummary @@ -68,7 +71,7 @@ class SpaceHierarchyTest : InstrumentedTest { var roomId = "" commonTestHelper.waitWithLatch { - roomId = session.createRoom(CreateRoomParams().apply { name = "General" }) + roomId = session.roomService().createRoom(CreateRoomParams().apply { name = "General" }) it.countDown() } @@ -174,21 +177,27 @@ class SpaceHierarchyTest : InstrumentedTest { val commonTestHelper = CommonTestHelper(context()) val session = commonTestHelper.createAccount("John", SessionTestParams(true)) - val spaceAInfo = createPublicSpace(session, "SpaceA", listOf( + val spaceAInfo = createPublicSpace( + session, "SpaceA", listOf( Triple("A1", true /*auto-join*/, true/*canonical*/), Triple("A2", true, true) - )) + ) + ) - /* val spaceBInfo = */ createPublicSpace(session, "SpaceB", listOf( + /* val spaceBInfo = */ createPublicSpace( + session, "SpaceB", listOf( Triple("B1", true /*auto-join*/, true/*canonical*/), Triple("B2", true, true), Triple("B3", true, true) - )) + ) + ) - val spaceCInfo = createPublicSpace(session, "SpaceC", listOf( + val spaceCInfo = createPublicSpace( + session, "SpaceC", listOf( Triple("C1", true /*auto-join*/, true/*canonical*/), Triple("C2", true, true) - )) + ) + ) // add C as a subspace of A val spaceA = session.spaceService().getSpace(spaceAInfo.spaceId) @@ -203,27 +212,27 @@ class SpaceHierarchyTest : InstrumentedTest { var orphan1 = "" commonTestHelper.waitWithLatch { - orphan1 = session.createRoom(CreateRoomParams().apply { name = "O1" }) + orphan1 = session.roomService().createRoom(CreateRoomParams().apply { name = "O1" }) it.countDown() } var orphan2 = "" commonTestHelper.waitWithLatch { - orphan2 = session.createRoom(CreateRoomParams().apply { name = "O2" }) + orphan2 = session.roomService().createRoom(CreateRoomParams().apply { name = "O2" }) it.countDown() } - val allRooms = session.getRoomSummaries(roomSummaryQueryParams { excludeType = listOf(RoomType.SPACE) }) + val allRooms = session.roomService().getRoomSummaries(roomSummaryQueryParams { excludeType = listOf(RoomType.SPACE) }) assertEquals("Unexpected number of rooms", 9, allRooms.size) - val orphans = session.getFlattenRoomSummaryChildrenOf(null) + val orphans = session.roomService().getFlattenRoomSummaryChildrenOf(null) assertEquals("Unexpected number of orphan rooms", 2, orphans.size) assertTrue("O1 should be an orphan", orphans.any { it.roomId == orphan1 }) assertTrue("O2 should be an orphan ${orphans.map { it.name }}", orphans.any { it.roomId == orphan2 }) - val aChildren = session.getFlattenRoomSummaryChildrenOf(spaceAInfo.spaceId) + val aChildren = session.roomService().getFlattenRoomSummaryChildrenOf(spaceAInfo.spaceId) assertEquals("Unexpected number of flatten child rooms", 4, aChildren.size) assertTrue("A1 should be a child of A", aChildren.any { it.name == "A1" }) @@ -233,13 +242,13 @@ class SpaceHierarchyTest : InstrumentedTest { // Add a non canonical child and check that it does not appear as orphan commonTestHelper.waitWithLatch { - val a3 = session.createRoom(CreateRoomParams().apply { name = "A3" }) + val a3 = session.roomService().createRoom(CreateRoomParams().apply { name = "A3" }) spaceA!!.addChildren(a3, viaServers, null, false) it.countDown() } Thread.sleep(6_000) - val orphansUpdate = session.getRoomSummaries(roomSummaryQueryParams { + val orphansUpdate = session.roomService().getRoomSummaries(roomSummaryQueryParams { activeSpaceFilter = ActiveSpaceFilter.ActiveSpace(null) }) assertEquals("Unexpected number of orphan rooms ${orphansUpdate.map { it.name }}", 2, orphansUpdate.size) @@ -251,15 +260,19 @@ class SpaceHierarchyTest : InstrumentedTest { val commonTestHelper = CommonTestHelper(context()) val session = commonTestHelper.createAccount("John", SessionTestParams(true)) - val spaceAInfo = createPublicSpace(session, "SpaceA", listOf( + val spaceAInfo = createPublicSpace( + session, "SpaceA", listOf( Triple("A1", true /*auto-join*/, true/*canonical*/), Triple("A2", true, true) - )) + ) + ) - val spaceCInfo = createPublicSpace(session, "SpaceC", listOf( + val spaceCInfo = createPublicSpace( + session, "SpaceC", listOf( Triple("C1", true /*auto-join*/, true/*canonical*/), Triple("C2", true, true) - )) + ) + ) // add C as a subspace of A val spaceA = session.spaceService().getSpace(spaceAInfo.spaceId) @@ -279,7 +292,7 @@ class SpaceHierarchyTest : InstrumentedTest { // A -> C -> A - val aChildren = session.getFlattenRoomSummaryChildrenOf(spaceAInfo.spaceId) + val aChildren = session.roomService().getFlattenRoomSummaryChildrenOf(spaceAInfo.spaceId) assertEquals("Unexpected number of flatten child rooms ${aChildren.map { it.name }}", 4, aChildren.size) assertTrue("A1 should be a child of A", aChildren.any { it.name == "A1" }) @@ -293,16 +306,20 @@ class SpaceHierarchyTest : InstrumentedTest { val commonTestHelper = CommonTestHelper(context()) val session = commonTestHelper.createAccount("John", SessionTestParams(true)) - val spaceAInfo = createPublicSpace(session, "SpaceA", listOf( + val spaceAInfo = createPublicSpace( + session, "SpaceA", listOf( Triple("A1", true /*auto-join*/, true/*canonical*/), Triple("A2", true, true) - )) + ) + ) - val spaceBInfo = createPublicSpace(session, "SpaceB", listOf( + val spaceBInfo = createPublicSpace( + session, "SpaceB", listOf( Triple("B1", true /*auto-join*/, true/*canonical*/), Triple("B2", true, true), Triple("B3", true, true) - )) + ) + ) // add B as a subspace of A val spaceA = session.spaceService().getSpace(spaceAInfo.spaceId) @@ -312,14 +329,16 @@ class SpaceHierarchyTest : InstrumentedTest { session.spaceService().setSpaceParent(spaceBInfo.spaceId, spaceAInfo.spaceId, true, viaServers) } - val spaceCInfo = createPublicSpace(session, "SpaceC", listOf( + val spaceCInfo = createPublicSpace( + session, "SpaceC", listOf( Triple("C1", true /*auto-join*/, true/*canonical*/), Triple("C2", true, true) - )) + ) + ) commonTestHelper.waitWithLatch { latch -> - val flatAChildren = session.getFlattenRoomSummaryChildrenOfLive(spaceAInfo.spaceId) + val flatAChildren = session.roomService().getFlattenRoomSummaryChildrenOfLive(spaceAInfo.spaceId) val childObserver = object : Observer> { override fun onChanged(children: List?) { // Log.d("## TEST", "Space A flat children update : ${children?.map { it.name }}") @@ -346,7 +365,7 @@ class SpaceHierarchyTest : InstrumentedTest { val bRoomId = spaceBInfo.roomIds.first() commonTestHelper.waitWithLatch { latch -> - val flatAChildren = session.getFlattenRoomSummaryChildrenOfLive(spaceAInfo.spaceId) + val flatAChildren = session.roomService().getFlattenRoomSummaryChildrenOfLive(spaceAInfo.spaceId) val childObserver = object : Observer> { override fun onChanged(children: List?) { System.out.println("## TEST | Space A flat children update : ${children?.map { it.name }}") @@ -359,7 +378,7 @@ class SpaceHierarchyTest : InstrumentedTest { } // part from b room - session.leaveRoom(bRoomId) + session.roomService().leaveRoom(bRoomId) // The room should have disapear from flat children flatAChildren.observeForever(childObserver) } @@ -385,7 +404,7 @@ class SpaceHierarchyTest : InstrumentedTest { val viaServers = listOf(session.sessionParams.homeServerHost ?: "") roomIds = childInfo.map { entry -> - session.createRoom(CreateRoomParams().apply { name = entry.first }) + session.roomService().createRoom(CreateRoomParams().apply { name = entry.first }) } roomIds.forEachIndexed { index, roomId -> syncedSpace!!.addChildren(roomId, viaServers, null, childInfo[index].second) @@ -414,8 +433,9 @@ class SpaceHierarchyTest : InstrumentedTest { roomIds = childInfo.map { entry -> val homeServerCapabilities = session + .homeServerCapabilitiesService() .getHomeServerCapabilities() - session.createRoom(CreateRoomParams().apply { + session.roomService().createRoom(CreateRoomParams().apply { name = entry.first this.featurePreset = RestrictedRoomPreset( homeServerCapabilities, @@ -442,21 +462,27 @@ class SpaceHierarchyTest : InstrumentedTest { val commonTestHelper = CommonTestHelper(context()) val session = commonTestHelper.createAccount("John", SessionTestParams(true)) - /* val spaceAInfo = */ createPublicSpace(session, "SpaceA", listOf( + /* val spaceAInfo = */ createPublicSpace( + session, "SpaceA", listOf( Triple("A1", true /*auto-join*/, true/*canonical*/), Triple("A2", true, true) - )) + ) + ) - val spaceBInfo = createPublicSpace(session, "SpaceB", listOf( + val spaceBInfo = createPublicSpace( + session, "SpaceB", listOf( Triple("B1", true /*auto-join*/, true/*canonical*/), Triple("B2", true, true), Triple("B3", true, true) - )) + ) + ) - val spaceCInfo = createPublicSpace(session, "SpaceC", listOf( + val spaceCInfo = createPublicSpace( + session, "SpaceC", listOf( Triple("C1", true /*auto-join*/, true/*canonical*/), Triple("C2", true, true) - )) + ) + ) val viaServers = listOf(session.sessionParams.homeServerHost ?: "") @@ -475,7 +501,9 @@ class SpaceHierarchyTest : InstrumentedTest { // + C // + c1, c2 - val rootSpaces = session.spaceService().getRootSpaceSummaries() + val rootSpaces = commonTestHelper.runBlockingTest { + session.spaceService().getRootSpaceSummaries() + } assertEquals("Unexpected number of root spaces ${rootSpaces.map { it.name }}", 2, rootSpaces.size) @@ -488,28 +516,30 @@ class SpaceHierarchyTest : InstrumentedTest { val aliceSession = commonTestHelper.createAccount("Alice", SessionTestParams(true)) val bobSession = commonTestHelper.createAccount("Bib", SessionTestParams(true)) - val spaceAInfo = createPrivateSpace(aliceSession, "Private Space A", listOf( + val spaceAInfo = createPrivateSpace( + aliceSession, "Private Space A", listOf( Triple("General", true /*suggested*/, true/*canonical*/), Triple("Random", true, true) - )) + ) + ) commonTestHelper.runBlockingTest { - aliceSession.getRoom(spaceAInfo.spaceId)!!.invite(bobSession.myUserId, null) + aliceSession.getRoom(spaceAInfo.spaceId)!!.membershipService().invite(bobSession.myUserId, null) } commonTestHelper.runBlockingTest { - bobSession.joinRoom(spaceAInfo.spaceId, null, emptyList()) + bobSession.roomService().joinRoom(spaceAInfo.spaceId, null, emptyList()) } var bobRoomId = "" commonTestHelper.waitWithLatch { - bobRoomId = bobSession.createRoom(CreateRoomParams().apply { name = "A Bob Room" }) - bobSession.getRoom(bobRoomId)!!.invite(aliceSession.myUserId) + bobRoomId = bobSession.roomService().createRoom(CreateRoomParams().apply { name = "A Bob Room" }) + bobSession.getRoom(bobRoomId)!!.membershipService().invite(aliceSession.myUserId) it.countDown() } commonTestHelper.runBlockingTest { - aliceSession.joinRoom(bobRoomId) + aliceSession.roomService().joinRoom(bobRoomId) } commonTestHelper.waitWithLatch { latch -> @@ -549,7 +579,7 @@ class SpaceHierarchyTest : InstrumentedTest { ?.setUserPowerLevel(aliceSession.myUserId, Role.Admin.value) ?.toContent() - room.sendStateEvent(EventType.STATE_ROOM_POWER_LEVELS, stateKey = "", newPowerLevelsContent!!) + room.stateService().sendStateEvent(EventType.STATE_ROOM_POWER_LEVELS, stateKey = "", newPowerLevelsContent!!) it.countDown() } diff --git a/matrix-sdk-android/src/debug/java/org/matrix/android/sdk/internal/network/interceptors/CurlLoggingInterceptor.kt b/matrix-sdk-android/src/debug/java/org/matrix/android/sdk/internal/network/interceptors/CurlLoggingInterceptor.kt index 3add757efa..6dd3553d02 100644 --- a/matrix-sdk-android/src/debug/java/org/matrix/android/sdk/internal/network/interceptors/CurlLoggingInterceptor.kt +++ b/matrix-sdk-android/src/debug/java/org/matrix/android/sdk/internal/network/interceptors/CurlLoggingInterceptor.kt @@ -37,7 +37,7 @@ import javax.inject.Inject */ @MatrixScope internal class CurlLoggingInterceptor @Inject constructor() : - Interceptor { + Interceptor { /** * Set any additional curl command options (see 'curl --help'). diff --git a/matrix-sdk-android/src/main/java/org/commonmark/ext/maths/internal/MathsHtmlNodeRenderer.kt b/matrix-sdk-android/src/main/java/org/commonmark/ext/maths/internal/MathsHtmlNodeRenderer.kt index 0efecbbe8a..83fcae7190 100644 --- a/matrix-sdk-android/src/main/java/org/commonmark/ext/maths/internal/MathsHtmlNodeRenderer.kt +++ b/matrix-sdk-android/src/main/java/org/commonmark/ext/maths/internal/MathsHtmlNodeRenderer.kt @@ -28,8 +28,12 @@ internal class MathsHtmlNodeRenderer(private val context: HtmlNodeRendererContex val display = node.javaClass == DisplayMaths::class.java val contents = node.firstChild // should be the only child val latex = (contents as Text).literal - val attributes = context.extendAttributes(node, if (display) "div" else "span", Collections.singletonMap("data-mx-maths", - latex)) + val attributes = context.extendAttributes( + node, if (display) "div" else "span", Collections.singletonMap( + "data-mx-maths", + latex + ) + ) html.tag(if (display) "div" else "span", attributes) html.tag("code") context.render(contents) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/Matrix.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/Matrix.kt index e7b4b766ad..979201706b 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/Matrix.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/Matrix.kt @@ -38,14 +38,18 @@ import org.matrix.android.sdk.internal.util.BackgroundDetectionObserver import org.matrix.android.sdk.internal.worker.MatrixWorkerFactory import org.matrix.olm.OlmManager import java.util.concurrent.Executors -import java.util.concurrent.atomic.AtomicBoolean import javax.inject.Inject /** * This is the main entry point to the matrix sdk. - * To get the singleton instance, use getInstance static method. + *
+ * + * The constructor creates a new instance of Matrix, it's recommended to manage this instance as a singleton. + * + * @param context the application context + * @param matrixConfiguration global configuration that will be used for every [org.matrix.android.sdk.api.session.Session] */ -class Matrix private constructor(context: Context, matrixConfiguration: MatrixConfiguration) { +class Matrix(context: Context, matrixConfiguration: MatrixConfiguration) { @Inject internal lateinit var legacySessionImporter: LegacySessionImporter @Inject internal lateinit var authenticationService: AuthenticationService @@ -60,89 +64,73 @@ class Matrix private constructor(context: Context, matrixConfiguration: MatrixCo @Inject internal lateinit var lightweightSettingsStorage: LightweightSettingsStorage init { - Monarchy.init(context) - DaggerMatrixComponent.factory().create(context, matrixConfiguration).inject(this) - if (context.applicationContext !is Configuration.Provider) { + val appContext = context.applicationContext + Monarchy.init(appContext) + DaggerMatrixComponent.factory().create(appContext, matrixConfiguration).inject(this) + if (appContext !is Configuration.Provider) { val configuration = Configuration.Builder() .setExecutor(Executors.newCachedThreadPool()) .setWorkerFactory(matrixWorkerFactory) .build() - WorkManager.initialize(context, configuration) + WorkManager.initialize(appContext, configuration) } ProcessLifecycleOwner.get().lifecycle.addObserver(backgroundDetectionObserver) } + /** + * Return the User Agent used for any request that the SDK is making to the homeserver. + * There is no way to change the user agent at the moment. + */ fun getUserAgent() = userAgentHolder.userAgent - fun authenticationService(): AuthenticationService { - return authenticationService - } + /** + * Return the AuthenticationService. + */ + fun authenticationService() = authenticationService + /** + * Return the RawService. + */ fun rawService() = rawService + /** + * Return the LightweightSettingsStorage. + */ fun lightweightSettingsStorage() = lightweightSettingsStorage + /** + * Return the HomeServerHistoryService. + */ fun homeServerHistoryService() = homeServerHistoryService - fun legacySessionImporter(): LegacySessionImporter { - return legacySessionImporter - } + /** + * Return the legacy session importer, useful if you want to migrate an app, which was using the legacy Matrix Android Sdk. + */ + fun legacySessionImporter() = legacySessionImporter - fun workerFactory(): WorkerFactory = matrixWorkerFactory + /** + * Get the worker factory. The returned value has to be provided to `WorkConfiguration.Builder()`. + */ + fun getWorkerFactory(): WorkerFactory = matrixWorkerFactory + /** + * Register an API interceptor, to be able to be notified when the specified API got a response. + */ fun registerApiInterceptorListener(path: ApiPath, listener: ApiInterceptorListener) { apiInterceptor.addListener(path, listener) } + /** + * Un-register an API interceptor. + */ fun unregisterApiInterceptorListener(path: ApiPath, listener: ApiInterceptorListener) { apiInterceptor.removeListener(path, listener) } companion object { - - private lateinit var instance: Matrix - private val isInit = AtomicBoolean(false) - /** - * Creates a new instance of Matrix, it's recommended to manage this instance as a singleton. - * To make use of the built in singleton use Matrix.initialize() and/or Matrix.getInstance(context) instead - **/ - fun createInstance(context: Context, matrixConfiguration: MatrixConfiguration): Matrix { - return Matrix(context.applicationContext, matrixConfiguration) - } - - /** - * Initializes a singleton instance of Matrix for the given MatrixConfiguration - * This instance will be returned by Matrix.getInstance(context) + * @return a String with details about the Matrix SDK version. */ - @Deprecated("Use Matrix.createInstance and manage the instance manually") - fun initialize(context: Context, matrixConfiguration: MatrixConfiguration) { - if (isInit.compareAndSet(false, true)) { - instance = Matrix(context.applicationContext, matrixConfiguration) - } - } - - /** - * Either provides an already initialized singleton Matrix instance or queries the application context for a MatrixConfiguration.Provider - * to lazily create and store the instance. - */ - @Suppress("deprecation") // suppressing warning as this method is unused but is still provided for SDK clients - @Deprecated("Use Matrix.createInstance and manage the instance manually") - fun getInstance(context: Context): Matrix { - if (isInit.compareAndSet(false, true)) { - val appContext = context.applicationContext - if (appContext is MatrixConfiguration.Provider) { - val matrixConfiguration = (appContext as MatrixConfiguration.Provider).providesMatrixConfiguration() - instance = Matrix(appContext, matrixConfiguration) - } else { - throw IllegalStateException("Matrix is not initialized properly." + - " If you want to manage your own Matrix instance use Matrix.createInstance" + - " otherwise you should call Matrix.initialize or let your application implement MatrixConfiguration.Provider.") - } - } - return instance - } - fun getSdkVersion(): String { return BuildConfig.SDK_VERSION + " (" + BuildConfig.GIT_SDK_REVISION + ")" } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/MatrixCallback.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/MatrixCallback.kt index 166ba8eab9..7cd450b5ad 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/MatrixCallback.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/MatrixCallback.kt @@ -23,7 +23,7 @@ package org.matrix.android.sdk.api interface MatrixCallback { /** - * On success method, default to no-op + * On success method, default to no-op. * @param data the data successfully returned from the async function */ fun onSuccess(data: T) { @@ -31,7 +31,7 @@ interface MatrixCallback { } /** - * On failure method, default to no-op + * On failure method, default to no-op. * @param failure the failure data returned from the async function */ fun onFailure(failure: Throwable) { @@ -40,6 +40,6 @@ interface MatrixCallback { } /** - * Basic no op implementation + * Basic no op implementation. */ class NoOpMatrixCallback : MatrixCallback diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/MatrixConfiguration.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/MatrixConfiguration.kt index f8472319fd..21106fba6c 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/MatrixConfiguration.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/MatrixConfiguration.kt @@ -46,7 +46,7 @@ data class MatrixConfiguration( */ val proxy: Proxy? = null, /** - * TLS versions and cipher suites limitation for unauthenticated requests + * TLS versions and cipher suites limitation for unauthenticated requests. */ val connectionSpec: ConnectionSpec = ConnectionSpec.RESTRICTED_TLS, /** @@ -62,16 +62,7 @@ data class MatrixConfiguration( */ val roomDisplayNameFallbackProvider: RoomDisplayNameFallbackProvider, /** - * Thread messages default enable/disabled value + * Thread messages default enable/disabled value. */ val threadMessagesEnabledDefault: Boolean = false, -) { - - /** - * Can be implemented by your Application class. - */ - @Deprecated("Use Matrix.createInstance and manage the instance manually instead of Matrix.getInstance") - interface Provider { - fun providesMatrixConfiguration(): MatrixConfiguration - } -} +) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/MatrixConstants.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/MatrixConstants.kt index 49520f3678..33a820ddde 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/MatrixConstants.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/MatrixConstants.kt @@ -17,7 +17,7 @@ package org.matrix.android.sdk.api /** - * This object define some global constants regarding the Matrix specification + * This object define some global constants regarding the Matrix specification. */ object MatrixConstants { /** diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/MatrixPatterns.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/MatrixPatterns.kt index 2a26b612fb..867e066e60 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/MatrixPatterns.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/MatrixPatterns.kt @@ -147,7 +147,7 @@ object MatrixPatterns { } /** - * Extract server name from a matrix id + * Extract server name from a matrix id. * * @param matrixId * @return null if not found or if matrixId is null @@ -172,7 +172,7 @@ object MatrixPatterns { } /** - * Return the domain form a userId + * Return the domain form a userId. * Examples: * - "@alice:domain.org".getDomain() will return "domain.org" * - "@bob:domain.org:3455".getDomain() will return "domain.org:3455" diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/MatrixUrls.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/MatrixUrls.kt index 4a41eaec4a..7958186f16 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/MatrixUrls.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/MatrixUrls.kt @@ -17,21 +17,21 @@ package org.matrix.android.sdk.api /** - * This class contains pattern to match Matrix Url, aka mxc urls + * This class contains pattern to match Matrix Url, aka mxc urls. */ object MatrixUrls { /** - * "mxc" scheme, including "://". So "mxc://" + * "mxc" scheme, including "://". So "mxc://". */ const val MATRIX_CONTENT_URI_SCHEME = "mxc://" /** - * Return true if the String starts with "mxc://" + * Return true if the String starts with "mxc://". */ fun String.isMxcUrl() = startsWith(MATRIX_CONTENT_URI_SCHEME) /** - * Remove the "mxc://" prefix. No op if the String is not a Mxc URL + * Remove the "mxc://" prefix. No op if the String is not a Mxc URL. */ fun String.removeMxcPrefix() = removePrefix(MATRIX_CONTENT_URI_SCHEME) } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/auth/AuthenticationService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/auth/AuthenticationService.kt index 9cb784c9c0..5a19df90c4 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/auth/AuthenticationService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/auth/AuthenticationService.kt @@ -40,12 +40,12 @@ interface AuthenticationService { suspend fun getLoginFlowOfSession(sessionId: String): LoginFlowResult /** - * Get a SSO url + * Get a SSO url. */ fun getSsoUrl(redirectUrl: String, deviceId: String?, providerId: String?): String? /** - * Get the sign in or sign up fallback URL + * Get the sign in or sign up fallback URL. */ fun getFallbackUrl(forSignIn: Boolean, deviceId: String?): String? @@ -64,17 +64,17 @@ interface AuthenticationService { fun getRegistrationWizard(): RegistrationWizard /** - * True when login and password has been sent with success to the homeserver + * True when login and password has been sent with success to the homeserver. */ val isRegistrationStarted: Boolean /** - * Cancel pending login or pending registration + * Cancel pending login or pending registration. */ suspend fun cancelPendingLoginOrRegistration() /** - * Reset all pending settings, including current HomeServerConnectionConfig + * Reset all pending settings, including current HomeServerConnectionConfig. */ suspend fun reset() @@ -91,20 +91,20 @@ interface AuthenticationService { fun getLastAuthenticatedSession(): Session? /** - * Create a session after a SSO successful login + * Create a session after a SSO successful login. */ suspend fun createSessionFromSso(homeServerConnectionConfig: HomeServerConnectionConfig, credentials: Credentials): Session /** - * Perform a wellknown request, using the domain from the matrixId + * Perform a wellknown request, using the domain from the matrixId. */ suspend fun getWellKnownData(matrixId: String, homeServerConnectionConfig: HomeServerConnectionConfig?): WellknownResult /** - * Authenticate with a matrixId and a password - * Usually call this after a successful call to getWellKnownData() + * Authenticate with a matrixId and a password. + * Usually call this after a successful call to getWellKnownData(). * @param homeServerConnectionConfig the information about the homeserver and other configuration * @param matrixId the matrixId of the user * @param password the password of the account diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/auth/TokenBasedAuth.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/auth/TokenBasedAuth.kt index e522352c38..2c99ced995 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/auth/TokenBasedAuth.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/auth/TokenBasedAuth.kt @@ -20,7 +20,7 @@ import com.squareup.moshi.JsonClass import org.matrix.android.sdk.api.auth.data.LoginFlowTypes /** - * This class provides the authentication data by using user and password + * This class provides the authentication data by using user and password. */ @JsonClass(generateAdapter = true) data class TokenBasedAuth( diff --git a/vector/src/main/java/im/vector/app/features/login/terms/UrlAndName.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/auth/UrlAndName.kt similarity index 87% rename from vector/src/main/java/im/vector/app/features/login/terms/UrlAndName.kt rename to matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/auth/UrlAndName.kt index f298aeca37..ca0123b832 100644 --- a/vector/src/main/java/im/vector/app/features/login/terms/UrlAndName.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/auth/UrlAndName.kt @@ -1,5 +1,5 @@ /* - * Copyright 2019 New Vector Ltd + * Copyright 2020 The Matrix.org Foundation C.I.C. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,7 +14,7 @@ * limitations under the License. */ -package im.vector.app.features.login.terms +package org.matrix.android.sdk.api.auth data class UrlAndName( val url: String, diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/auth/UserPasswordAuth.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/auth/UserPasswordAuth.kt index e985c5f08a..8cd5b5e6eb 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/auth/UserPasswordAuth.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/auth/UserPasswordAuth.kt @@ -20,7 +20,7 @@ import com.squareup.moshi.JsonClass import org.matrix.android.sdk.api.auth.data.LoginFlowTypes /** - * This class provides the authentication data by using user and password + * This class provides the authentication data by using user and password. */ @JsonClass(generateAdapter = true) data class UserPasswordAuth( diff --git a/vector/src/main/java/im/vector/app/features/login/terms/converter.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/auth/converter.kt similarity index 90% rename from vector/src/main/java/im/vector/app/features/login/terms/converter.kt rename to matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/auth/converter.kt index 64e4fb65d9..80630bc4e7 100644 --- a/vector/src/main/java/im/vector/app/features/login/terms/converter.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/auth/converter.kt @@ -1,5 +1,5 @@ /* - * Copyright 2019 New Vector Ltd + * Copyright 2020 The Matrix.org Foundation C.I.C. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,7 +14,7 @@ * limitations under the License. */ -package im.vector.app.features.login.terms +package org.matrix.android.sdk.api.auth import org.matrix.android.sdk.api.auth.data.LocalizedFlowDataLoginTerms import org.matrix.android.sdk.api.auth.registration.TermPolicies @@ -102,12 +102,14 @@ fun TermPolicies.toLocalizedLoginTerms(userLanguage: String, } } - result.add(LocalizedFlowDataLoginTerms( - policyName = localizedFlowDataLoginTermsPolicyName, - version = localizedFlowDataLoginTermsVersion, - localizedUrl = localizedFlowDataLoginTermsLocalizedUrl, - localizedName = localizedFlowDataLoginTermsLocalizedName - )) + result.add( + LocalizedFlowDataLoginTerms( + policyName = localizedFlowDataLoginTermsPolicyName, + version = localizedFlowDataLoginTermsVersion, + localizedUrl = localizedFlowDataLoginTermsLocalizedUrl, + localizedName = localizedFlowDataLoginTermsLocalizedName + ) + ) } } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/auth/data/Credentials.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/auth/data/Credentials.kt index 317acccfb5..e3728753ad 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/auth/data/Credentials.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/auth/data/Credentials.kt @@ -37,7 +37,7 @@ data class Credentials( */ @Json(name = "access_token") val accessToken: String, /** - * Not documented + * Not documented. */ @Json(name = "refresh_token") val refreshToken: String?, /** diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/auth/data/SessionParams.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/auth/data/SessionParams.kt index b490ac877e..e3815231d9 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/auth/data/SessionParams.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/auth/data/SessionParams.kt @@ -22,12 +22,12 @@ package org.matrix.android.sdk.api.auth.data */ data class SessionParams( /** - * Please consider using shortcuts instead + * Please consider using shortcuts instead. */ val credentials: Credentials, /** - * Please consider using shortcuts instead + * Please consider using shortcuts instead. */ val homeServerConnectionConfig: HomeServerConnectionConfig, @@ -41,12 +41,12 @@ data class SessionParams( */ /** - * The userId of the session (Ex: "@user:domain.org") + * The userId of the session (Ex: "@user:domain.org"). */ val userId = credentials.userId /** - * The deviceId of the session (Ex: "ABCDEFGH") + * The deviceId of the session (Ex: "ABCDEFGH"). */ val deviceId = credentials.deviceId @@ -62,12 +62,12 @@ data class SessionParams( val homeServerUrlBase = homeServerConnectionConfig.homeServerUriBase.toString() /** - * The current homeserver host, using what has been entered by the user during login phase + * The current homeserver host, using what has been entered by the user during login phase. */ val homeServerHost = homeServerConnectionConfig.homeServerUri.host /** - * The default identity server url if any, returned by the homeserver during login phase + * The default identity server url if any, returned by the homeserver during login phase. */ val defaultIdentityServerUrl = homeServerConnectionConfig.identityServerUri?.toString() } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/auth/data/WellKnown.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/auth/data/WellKnown.kt index 4bcef60605..10c7d51392 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/auth/data/WellKnown.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/auth/data/WellKnown.kt @@ -43,6 +43,7 @@ import org.matrix.android.sdk.api.util.JsonDict * } * } * + * . */ @JsonClass(generateAdapter = true) data class WellKnown( diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/auth/data/WellKnownBaseConfig.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/auth/data/WellKnownBaseConfig.kt index ad6b9970de..df00099232 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/auth/data/WellKnownBaseConfig.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/auth/data/WellKnownBaseConfig.kt @@ -26,6 +26,7 @@ import com.squareup.moshi.JsonClass * "base_url": "https://vector.im" * } * + * . */ @JsonClass(generateAdapter = true) data class WellKnownBaseConfig( diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/auth/login/LoginWizard.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/auth/login/LoginWizard.kt index 247d58ce79..3232025de3 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/auth/login/LoginWizard.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/auth/login/LoginWizard.kt @@ -26,7 +26,7 @@ import org.matrix.android.sdk.api.util.JsonDict */ interface LoginWizard { /** - * Get some information about a matrixId: displayName and avatar url + * Get some information about a matrixId: displayName and avatar url. */ suspend fun getProfileInfo(matrixId: String): LoginProfileInfo diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/auth/registration/RegistrationFlowResponse.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/auth/registration/RegistrationFlowResponse.kt index 0da9eb4b7e..1252e93b84 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/auth/registration/RegistrationFlowResponse.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/auth/registration/RegistrationFlowResponse.kt @@ -73,7 +73,7 @@ data class RegistrationFlowResponse( ) /** - * Convert to something easier to handle on client side + * Convert to something easier to handle on client side. */ fun RegistrationFlowResponse.toFlowResult(): FlowResult { // Get all the returned stages @@ -88,8 +88,10 @@ fun RegistrationFlowResponse.toFlowResult(): FlowResult { val isMandatory = flows?.all { type in it.stages.orEmpty() } == true val stage = when (type) { - LoginFlowTypes.RECAPTCHA -> Stage.ReCaptcha(isMandatory, ((params?.get(type) as? Map<*, *>)?.get("public_key") as? String) - ?: "") + LoginFlowTypes.RECAPTCHA -> Stage.ReCaptcha( + isMandatory, ((params?.get(type) as? Map<*, *>)?.get("public_key") as? String) + ?: "" + ) LoginFlowTypes.DUMMY -> Stage.Dummy(isMandatory) LoginFlowTypes.TERMS -> Stage.Terms(isMandatory, params?.get(type) as? TermPolicies ?: emptyMap()) LoginFlowTypes.EMAIL_IDENTITY -> Stage.Email(isMandatory) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/auth/registration/RegistrationWizard.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/auth/registration/RegistrationWizard.kt index 621253faa5..0cda64499f 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/auth/registration/RegistrationWizard.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/auth/registration/RegistrationWizard.kt @@ -16,6 +16,8 @@ package org.matrix.android.sdk.api.auth.registration +import org.matrix.android.sdk.api.util.JsonDict + /** * Set of methods to be able to create an account on a homeserver. * @@ -73,6 +75,13 @@ interface RegistrationWizard { */ suspend fun dummy(): RegistrationResult + /** + * Perform custom registration stage by sending a custom JsonDict. + * Current registration "session" param will be included into authParams by default. + * The authParams should contain at least one entry "type" with a String value. + */ + suspend fun registrationCustom(authParams: JsonDict): RegistrationResult + /** * Perform the "m.login.email.identity" or "m.login.msisdn" stage. * diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/crypto/CryptoConstants.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/crypto/CryptoConstants.kt index 172cfa8360..37b9ac379e 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/crypto/CryptoConstants.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/crypto/CryptoConstants.kt @@ -32,7 +32,7 @@ const val MXCRYPTO_ALGORITHM_MEGOLM = "m.megolm.v1.aes-sha2" const val MXCRYPTO_ALGORITHM_MEGOLM_BACKUP = "m.megolm_backup.v1.curve25519-aes-sha2" /** - * Secured Shared Storage algorithm constant + * Secured Shared Storage algorithm constant. */ const val SSSS_ALGORITHM_CURVE25519_AES_SHA2 = "m.secret_storage.v1.curve25519-aes-sha2" diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/crypto/Emojis.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/crypto/Emojis.kt index 9c0eb10970..85d6961384 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/crypto/Emojis.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/crypto/Emojis.kt @@ -20,7 +20,7 @@ import org.matrix.android.sdk.api.session.crypto.verification.EmojiRepresentatio import org.matrix.android.sdk.internal.crypto.verification.getEmojiForCode /** - * Provide all the emojis used for SAS verification (for debug purpose) + * Provide all the emojis used for SAS verification (for debug purpose). */ fun getAllVerificationEmojis(): List { return (0..63).map { getEmojiForCode(it) } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/crypto/MXCryptoConfig.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/crypto/MXCryptoConfig.kt index 9a686de2e1..9507ddda65 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/crypto/MXCryptoConfig.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/crypto/MXCryptoConfig.kt @@ -31,5 +31,11 @@ data class MXCryptoConfig constructor( * If set to false, the request will be forwarded to the application layer; in this * case the application can decide to prompt the user. */ - val discardRoomKeyRequestsFromUntrustedDevices: Boolean = true + val discardRoomKeyRequestsFromUntrustedDevices: Boolean = true, + + /** + * Currently megolm keys are requested to the sender device and to all of our devices. + * You can limit request only to your sessions by turning this setting to `true` + */ + val limitRoomKeyRequestsToMyDevices: Boolean = false, ) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/extensions/Strings.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/extensions/Strings.kt index e264843ea4..5e1350e327 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/extensions/Strings.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/extensions/Strings.kt @@ -24,6 +24,6 @@ fun CharSequence.ensurePrefix(prefix: CharSequence): CharSequence { } /** - * Append a new line and then the provided string + * Append a new line and then the provided string. */ fun StringBuilder.appendNl(str: String) = append("\n").append(str) 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 362ebcec26..d3cc8fc8e4 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 @@ -47,7 +47,7 @@ fun Throwable.shouldBeRetried() = this is Failure.NetworkConnection || isLimitExceededError() /** - * Get the retry delay in case of rate limit exceeded error, adding 100 ms, of defaultValue otherwise + * Get the retry delay in case of rate limit exceeded error, adding 100 ms, of defaultValue otherwise. */ fun Throwable.getRetryDelay(defaultValue: Long): Long { return (this as? Failure.ServerError) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/failure/GlobalError.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/failure/GlobalError.kt index b5165b6687..5b4896f95f 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/failure/GlobalError.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/failure/GlobalError.kt @@ -23,5 +23,10 @@ sealed class GlobalError { data class InvalidToken(val softLogout: Boolean) : GlobalError() data class ConsentNotGivenError(val consentUri: String) : GlobalError() data class CertificateError(val fingerprint: Fingerprint) : GlobalError() + + /** + * The SDK requires the app (which should request the user) to perform an initial sync. + */ + data class InitialSyncRequest(val reason: InitialSyncRequestReason) : GlobalError() object ExpiredAccount : GlobalError() } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/IncomingShareRequestCommon.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/failure/InitialSyncRequestReason.kt similarity index 61% rename from matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/IncomingShareRequestCommon.kt rename to matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/failure/InitialSyncRequestReason.kt index 97c369db3e..8bebbcc3a5 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/IncomingShareRequestCommon.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/failure/InitialSyncRequestReason.kt @@ -1,5 +1,5 @@ /* - * Copyright 2020 The Matrix.org Foundation C.I.C. + * Copyright (c) 2022 The Matrix.org Foundation C.I.C. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,23 +14,14 @@ * limitations under the License. */ -package org.matrix.android.sdk.internal.crypto +package org.matrix.android.sdk.api.failure -internal interface IncomingShareRequestCommon { +/** + * This enum provide the reason why the SDK request an initial sync to the application. + */ +enum class InitialSyncRequestReason { /** - * The user id + * The list of ignored users has changed, and at least one user who was ignored is not ignored anymore. */ - val userId: String? - - /** - * The device id - */ - val deviceId: String? - - /** - * The request id - */ - val requestId: String? - - val localCreationTimestamp: Long? + IGNORED_USERS_LIST_CHANGE, } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/failure/MatrixError.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/failure/MatrixError.kt index 32e1aca17d..3dbbc39564 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/failure/MatrixError.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/failure/MatrixError.kt @@ -28,9 +28,9 @@ import org.matrix.android.sdk.api.util.JsonDict */ @JsonClass(generateAdapter = true) data class MatrixError( - /** unique string which can be used to handle an error message */ + /** unique string which can be used to handle an error message. */ @Json(name = "errcode") val code: String, - /** human-readable error message */ + /** human-readable error message. */ @Json(name = "error") val message: String, // For M_CONSENT_NOT_GIVEN @@ -92,19 +92,19 @@ data class MatrixError( /** Sent when the room alias given to the createRoom API is already in use. */ const val M_ROOM_IN_USE = "M_ROOM_IN_USE" - /** (Not documented yet) */ + /** (Not documented yet). */ const val M_BAD_PAGINATION = "M_BAD_PAGINATION" /** The request was not correctly authorized. Usually due to login failures. */ const val M_UNAUTHORIZED = "M_UNAUTHORIZED" - /** (Not documented yet) */ + /** (Not documented yet). */ const val M_OLD_VERSION = "M_OLD_VERSION" /** The server did not understand the request. */ const val M_UNRECOGNIZED = "M_UNRECOGNIZED" - /** (Not documented yet) */ + /** (Not documented yet). */ const val M_LOGIN_EMAIL_URL_NOT_YET = "M_LOGIN_EMAIL_URL_NOT_YET" /** Authentication could not be performed on the third party identifier. */ @@ -122,7 +122,7 @@ data class MatrixError( /** The request or entity was too large. */ const val M_TOO_LARGE = "M_TOO_LARGE" - /** (Not documented yet) */ + /** (Not documented yet). */ const val M_CONSENT_NOT_GIVEN = "M_CONSENT_NOT_GIVEN" /** The request cannot be completed because the homeserver has reached a resource limit imposed on it. For example, @@ -176,10 +176,10 @@ data class MatrixError( /** The user is unable to reject an invite to join the server notices room. See the Server Notices module for more information. */ const val M_CANNOT_LEAVE_SERVER_NOTICE_ROOM = "M_CANNOT_LEAVE_SERVER_NOTICE_ROOM" - /** (Not documented yet) */ + /** (Not documented yet). */ const val M_WRONG_ROOM_KEYS_VERSION = "M_WRONG_ROOM_KEYS_VERSION" - /** (Not documented yet) */ + /** (Not documented yet). */ const val M_WEAK_PASSWORD = "M_WEAK_PASSWORD" /** The provided password's length is shorter than the minimum length required by the server. */ diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/federation/FederationService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/federation/FederationService.kt index 0761ef8d21..8fe6460753 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/federation/FederationService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/federation/FederationService.kt @@ -18,7 +18,7 @@ package org.matrix.android.sdk.api.federation interface FederationService { /** - * Get information about the homeserver + * Get information about the homeserver. */ suspend fun getFederationVersion(): FederationVersion } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/listeners/ProgressListener.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/listeners/ProgressListener.kt index 02ebd7f784..ec55348dc5 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/listeners/ProgressListener.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/listeners/ProgressListener.kt @@ -17,7 +17,7 @@ package org.matrix.android.sdk.api.listeners /** - * Interface to send a progress info + * Interface to send a progress info. */ interface ProgressListener { /** diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/listeners/StepProgressListener.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/listeners/StepProgressListener.kt index 0fabfed2ff..4b87507c02 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/listeners/StepProgressListener.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/listeners/StepProgressListener.kt @@ -17,7 +17,7 @@ package org.matrix.android.sdk.api.listeners /** - * Interface to send a progress info + * Interface to send a progress info. */ interface StepProgressListener { diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/logger/LoggerTag.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/logger/LoggerTag.kt index 44ac439d7b..ae65963f37 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/logger/LoggerTag.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/logger/LoggerTag.kt @@ -22,15 +22,15 @@ package org.matrix.android.sdk.api.logger * val loggerTag = LoggerTag("MyTag", LoggerTag.VOIP) * Timber.tag(loggerTag.value).v("My log message") */ -open class LoggerTag(_value: String, parentTag: LoggerTag? = null) { +open class LoggerTag(name: String, parentTag: LoggerTag? = null) { object SYNC : LoggerTag("SYNC") object VOIP : LoggerTag("VOIP") object CRYPTO : LoggerTag("CRYPTO") val value: String = if (parentTag == null) { - _value + name } else { - "${parentTag.value}/$_value" + "${parentTag.value}/$name" } } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/query/QueryStringValue.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/query/QueryStringValue.kt index 31ec131c5c..368ff98661 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/query/QueryStringValue.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/query/QueryStringValue.kt @@ -36,19 +36,19 @@ sealed interface QueryStringValue { enum class Case { /** - * Match query sensitive to case + * Match query sensitive to case. */ SENSITIVE, /** - * Match query insensitive to case, this only works for Latin-1 character sets + * Match query insensitive to case, this only works for Latin-1 character sets. */ INSENSITIVE, /** - * Match query with input normalized (case insensitive) - * Works around Realms inability to sort or filter by case for non Latin-1 character sets - * Expects the target field to contain normalized data + * Match query with input normalized (case insensitive). + * Works around Realms inability to sort or filter by case for non Latin-1 character sets. + * Expects the target field to contain normalized data. * * @see org.matrix.android.sdk.internal.util.Normalizer.normalize */ diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/raw/RawService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/raw/RawService.kt index 3366d040f7..71a4f84a8d 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/raw/RawService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/raw/RawService.kt @@ -23,19 +23,19 @@ import org.matrix.android.sdk.api.cache.CacheStrategy */ interface RawService { /** - * Get a URL, either from cache or from the remote server, depending on the cache strategy + * Get a URL, either from cache or from the remote server, depending on the cache strategy. */ suspend fun getUrl(url: String, cacheStrategy: CacheStrategy): String /** - * Specific case for the well-known file. Cache validity is 8 hours + * Specific case for the well-known file. Cache validity is 8 hours. * @param domain the domain to get the .well-known file, for instance "matrix.org". * The URL will be "https://{domain}/.well-known/matrix/client" */ suspend fun getWellknown(domain: String): String /** - * Clear all the cache data + * Clear all the cache data. */ suspend fun clearCache() } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/Session.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/Session.kt index be924e2063..2f1ae8cd87 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/Session.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/Session.kt @@ -24,10 +24,8 @@ import org.matrix.android.sdk.api.MatrixCoroutineDispatchers import org.matrix.android.sdk.api.auth.data.SessionParams import org.matrix.android.sdk.api.failure.GlobalError import org.matrix.android.sdk.api.federation.FederationService -import org.matrix.android.sdk.api.pushrules.PushRuleService import org.matrix.android.sdk.api.session.account.AccountService import org.matrix.android.sdk.api.session.accountdata.SessionAccountDataService -import org.matrix.android.sdk.api.session.cache.CacheService import org.matrix.android.sdk.api.session.call.CallSignalingService import org.matrix.android.sdk.api.session.content.ContentUploadStateTracker import org.matrix.android.sdk.api.session.content.ContentUrlResolver @@ -47,6 +45,7 @@ import org.matrix.android.sdk.api.session.permalinks.PermalinkService import org.matrix.android.sdk.api.session.presence.PresenceService import org.matrix.android.sdk.api.session.profile.ProfileService import org.matrix.android.sdk.api.session.pushers.PushersService +import org.matrix.android.sdk.api.session.pushrules.PushRuleService import org.matrix.android.sdk.api.session.room.RoomDirectoryService import org.matrix.android.sdk.api.session.room.RoomService import org.matrix.android.sdk.api.session.search.SearchService @@ -68,47 +67,28 @@ import org.matrix.android.sdk.api.session.widgets.WidgetService * This interface defines interactions with a session. * An instance of a session will be provided by the SDK. */ -interface Session : - RoomService, - RoomDirectoryService, - GroupService, - UserService, - CacheService, - SignOutService, - FilterService, - TermsService, - EventService, - ProfileService, - PresenceService, - PushRuleService, - PushersService, - SyncStatusService, - HomeServerCapabilitiesService, - SecureStorageService, - AccountService, - ToDeviceService, - EventStreamService { +interface Session { val coroutineDispatchers: MatrixCoroutineDispatchers /** - * The params associated to the session + * The params associated to the session. */ val sessionParams: SessionParams /** - * The session is valid, i.e. it has a valid token so far + * The session is valid, i.e. it has a valid token so far. */ val isOpenable: Boolean /** - * Useful shortcut to get access to the userId + * Useful shortcut to get access to the userId. */ val myUserId: String get() = sessionParams.userId /** - * The sessionId + * The sessionId. */ val sessionId: String @@ -119,16 +99,16 @@ interface Session : fun open() /** - * Requires a one time background sync + * Requires a one time background sync. */ fun requireBackgroundSync() /** - * Launches infinite self rescheduling background syncs via the WorkManager + * Launches infinite self rescheduling background syncs via the WorkManager. * - * While dozing, syncs will only occur during maintenance windows + * While dozing, syncs will only occur during maintenance windows. * For reliability it's recommended to also start a long running foreground service - * along with disabling battery optimizations + * along with disabling battery optimizations. */ fun startAutomaticBackgroundSync(timeOutInSeconds: Long, repeatDelayInSeconds: Long) @@ -144,6 +124,11 @@ interface Session : */ fun stopSync() + /** + * Clear cache of the session. + */ + suspend fun clearCache() + /** * This method allows to listen the sync state. * @return a [LiveData] of [SyncState]. @@ -162,7 +147,7 @@ interface Session : fun syncFlow(): SharedFlow /** - * This methods return true if an initial sync has been processed + * This methods return true if an initial sync has been processed. */ fun hasAlreadySynced(): Boolean @@ -177,95 +162,190 @@ interface Session : fun contentUrlResolver(): ContentUrlResolver /** - * Returns the ContentUploadProgressTracker associated with the session + * Returns the ContentUploadProgressTracker associated with the session. */ fun contentUploadProgressTracker(): ContentUploadStateTracker /** - * Returns the TypingUsersTracker associated with the session + * Returns the TypingUsersTracker associated with the session. */ fun typingUsersTracker(): TypingUsersTracker /** - * Returns the ContentDownloadStateTracker associated with the session + * Returns the ContentDownloadStateTracker associated with the session. */ fun contentDownloadProgressTracker(): ContentDownloadStateTracker /** - * Returns the cryptoService associated with the session + * Returns the cryptoService associated with the session. */ fun cryptoService(): CryptoService /** - * Returns the ContentScannerService associated with the session + * Returns the ContentScannerService associated with the session. */ fun contentScannerService(): ContentScannerService /** - * Returns the identity service associated with the session + * Returns the identity service associated with the session. */ fun identityService(): IdentityService /** - * Returns the widget service associated with the session + * Returns the HomeServerCapabilities service associated with the session. + */ + fun homeServerCapabilitiesService(): HomeServerCapabilitiesService + + /** + * Returns the RoomService associated with the session. + */ + fun roomService(): RoomService + + /** + * Returns the RoomDirectoryService associated with the session. + */ + fun roomDirectoryService(): RoomDirectoryService + + /** + * Returns the GroupService associated with the session. + */ + fun groupService(): GroupService + + /** + * Returns the UserService associated with the session. + */ + fun userService(): UserService + + /** + * Returns the SignOutService associated with the session. + */ + fun signOutService(): SignOutService + + /** + * Returns the FilterService associated with the session. + */ + fun filterService(): FilterService + + /** + * Returns the PushRuleService associated with the session. + */ + fun pushRuleService(): PushRuleService + + /** + * Returns the PushersService associated with the session. + */ + fun pushersService(): PushersService + + /** + * Returns the EventService associated with the session. + */ + fun eventService(): EventService + + /** + * Returns the TermsService associated with the session. + */ + fun termsService(): TermsService + + /** + * Returns the SyncStatusService associated with the session. + */ + fun syncStatusService(): SyncStatusService + + /** + * Returns the SecureStorageService associated with the session. + */ + fun secureStorageService(): SecureStorageService + + /** + * Returns the ProfileService associated with the session. + */ + fun profileService(): ProfileService + + /** + * Returns the PresenceService associated with the session. + */ + fun presenceService(): PresenceService + + /** + * Returns the AccountService associated with the session. + */ + fun accountService(): AccountService + + /** + * Returns the ToDeviceService associated with the session. + */ + fun toDeviceService(): ToDeviceService + + /** + * Returns the EventStreamService associated with the session. + */ + fun eventStreamService(): EventStreamService + + /** + * Returns the widget service associated with the session. */ fun widgetService(): WidgetService /** - * Returns the media service associated with the session + * Returns the media service associated with the session. */ fun mediaService(): MediaService /** - * Returns the integration manager service associated with the session + * Returns the integration manager service associated with the session. */ fun integrationManagerService(): IntegrationManagerService /** - * Returns the call signaling service associated with the session + * Returns the call signaling service associated with the session. */ fun callSignalingService(): CallSignalingService /** - * Returns the file download service associated with the session + * Returns the file download service associated with the session. */ fun fileService(): FileService /** - * Returns the permalink service associated with the session + * Returns the permalink service associated with the session. */ fun permalinkService(): PermalinkService /** - * Returns the search service associated with the session + * Returns the search service associated with the session. */ fun searchService(): SearchService /** - * Returns the federation service associated with the session + * Returns the federation service associated with the session. */ fun federationService(): FederationService /** - * Returns the third party service associated with the session + * Returns the third party service associated with the session. */ fun thirdPartyService(): ThirdPartyService /** - * Returns the space service associated with the session + * Returns the space service associated with the session. */ fun spaceService(): SpaceService /** - * Returns the open id service associated with the session + * Returns the open id service associated with the session. */ fun openIdService(): OpenIdService /** - * Returns the account data service associated with the session + * Returns the account data service associated with the session. */ fun accountDataService(): SessionAccountDataService + /** + * Returns the SharedSecretStorageService associated with the session. + */ + fun sharedSecretStorageService(): SharedSecretStorageService + /** * Add a listener to the session. * @param listener the listener to add. @@ -297,17 +377,16 @@ interface Session : /** * Possible cases: * - The access token is not valid anymore, - * - a M_CONSENT_NOT_GIVEN error has been received from the homeserver + * - a M_CONSENT_NOT_GIVEN error has been received from the homeserver; + * See [GlobalError] for all the possible cases. */ fun onGlobalError(session: Session, globalError: GlobalError) = Unit } - val sharedSecretStorageService: SharedSecretStorageService - fun getUiaSsoFallbackUrl(authenticationSessionId: String): String /** - * Maintenance API, allows to print outs info on DB size to logcat + * Maintenance API, allows to print outs info on DB size to logcat. */ fun logDbUsageInfo() } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/SessionExtensions.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/SessionExtensions.kt new file mode 100644 index 0000000000..a15e73eb88 --- /dev/null +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/SessionExtensions.kt @@ -0,0 +1,36 @@ +/* + * Copyright (c) 2022 The Matrix.org Foundation C.I.C. + * + * 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 org.matrix.android.sdk.api.session + +import org.matrix.android.sdk.api.session.room.Room +import org.matrix.android.sdk.api.session.room.model.RoomSummary +import org.matrix.android.sdk.api.session.user.model.User + +/** + * Get a room using the RoomService of a Session. + */ +fun Session.getRoom(roomId: String): Room? = roomService().getRoom(roomId) + +/** + * Get a room summary using the RoomService of a Session. + */ +fun Session.getRoomSummary(roomIdOrAlias: String): RoomSummary? = roomService().getRoomSummary(roomIdOrAlias) + +/** + * Get a user using the UserService of a Session. + */ +fun Session.getUser(userId: String): User? = userService().getUser(userId) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/ToDeviceService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/ToDeviceService.kt index d7afad5b6c..56e5ebdbe7 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/ToDeviceService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/ToDeviceService.kt @@ -21,9 +21,8 @@ import org.matrix.android.sdk.api.session.events.model.Content import java.util.UUID interface ToDeviceService { - /** - * Send an event to a specific list of devices + * Send an event to a specific list of devices. */ suspend fun sendToDevice(eventType: String, contentMap: MXUsersDevicesMap, txnId: String? = UUID.randomUUID().toString()) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/accountdata/SessionAccountDataService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/accountdata/SessionAccountDataService.kt index 2ffb9112d1..a22dd33774 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/accountdata/SessionAccountDataService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/accountdata/SessionAccountDataService.kt @@ -26,12 +26,12 @@ import org.matrix.android.sdk.api.util.Optional */ interface SessionAccountDataService { /** - * Retrieve the account data with the provided type or null if not found + * Retrieve the account data with the provided type or null if not found. */ fun getUserAccountDataEvent(type: String): UserAccountDataEvent? /** - * Observe the account data with the provided type + * Observe the account data with the provided type. */ fun getLiveUserAccountDataEvent(type: String): LiveData> @@ -60,7 +60,7 @@ interface SessionAccountDataService { fun getLiveRoomAccountDataEvents(types: Set): LiveData> /** - * Update the account data with the provided type and the provided account data content + * Update the account data with the provided type and the provided account data content. */ suspend fun updateUserAccountData(type: String, content: Content) } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/call/CallListener.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/call/CallListener.kt index d17be59cd4..7f932e8815 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/call/CallListener.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/call/CallListener.kt @@ -39,32 +39,32 @@ interface CallListener { fun onCallAnswerReceived(callAnswerContent: CallAnswerContent) /** - * Called when a called has been hung up + * Called when a called has been hung up. */ fun onCallHangupReceived(callHangupContent: CallHangupContent) /** - * Called when a called has been rejected + * Called when a called has been rejected. */ fun onCallRejectReceived(callRejectContent: CallRejectContent) /** - * Called when an answer has been selected + * Called when an answer has been selected. */ fun onCallSelectAnswerReceived(callSelectAnswerContent: CallSelectAnswerContent) /** - * Called when a negotiation is sent + * Called when a negotiation is sent. */ fun onCallNegotiateReceived(callNegotiateContent: CallNegotiateContent) /** - * Called when the call has been managed by an other session + * Called when the call has been managed by an other session. */ fun onCallManagedByOtherSession(callId: String) /** - * Called when an asserted identity event is received + * Called when an asserted identity event is received. */ fun onCallAssertedIdentityReceived(callAssertedIdentityContent: CallAssertedIdentityContent) } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/call/CallSignalingService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/call/CallSignalingService.kt index c34744e75f..e17c02c40a 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/call/CallSignalingService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/call/CallSignalingService.kt @@ -21,7 +21,7 @@ interface CallSignalingService { suspend fun getTurnServer(): TurnServerResponse /** - * Create an outgoing call + * Create an outgoing call. */ fun createOutgoingCall(roomId: String, otherUserId: String, isVideoCall: Boolean): MxCall diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/call/CallState.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/call/CallState.kt index ff1df63300..4bf78c9d6a 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/call/CallState.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/call/CallState.kt @@ -20,7 +20,7 @@ import org.matrix.android.sdk.api.session.room.model.call.EndCallReason sealed class CallState { - /** Idle, setting up objects */ + /** Idle, setting up objects. */ object Idle : CallState() /** diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/call/MxCall.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/call/MxCall.kt index dd23e81cc6..e13f7310e0 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/call/MxCall.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/call/MxCall.kt @@ -35,7 +35,7 @@ interface MxCallDetail { } /** - * Define both an incoming call and on outgoing call + * Define both an incoming call and on outgoing call. */ interface MxCall : MxCallDetail { @@ -46,13 +46,13 @@ interface MxCall : MxCallDetail { var state: CallState /** - * Pick Up the incoming call - * It has no effect on outgoing call + * Pick Up the incoming call. + * It has no effect on outgoing call. */ fun accept(sdpString: String) /** - * SDP negotiation for media pause, hold/resume, ICE restarts and voice/video call up/downgrading + * SDP negotiation for media pause, hold/resume, ICE restarts and voice/video call up/downgrading. */ fun negotiate(sdpString: String, type: SdpType) @@ -62,17 +62,17 @@ interface MxCall : MxCallDetail { fun selectAnswer() /** - * Reject an incoming call + * Reject an incoming call. */ fun reject() /** - * End the call + * End the call. */ fun hangUp(reason: EndCallReason? = null) /** - * Start a call + * Start a call. * Send offer SDP to the other participant. */ fun offerSdp(sdpString: String) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/content/ContentUrlResolver.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/content/ContentUrlResolver.kt index 523d60359b..e59e676ed9 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/content/ContentUrlResolver.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/content/ContentUrlResolver.kt @@ -29,7 +29,7 @@ interface ContentUrlResolver { } /** - * URL to use to upload content + * URL to use to upload content. */ val uploadUrl: String @@ -42,7 +42,7 @@ interface ContentUrlResolver { fun resolveFullSize(contentUrl: String?): String? /** - * Get the ResolvedMethod to download a URL + * Get the ResolvedMethod to download a URL. * * @param contentUrl the Matrix media content URI (in the form of "mxc://..."). * @param elementToDecrypt Encryption data may be required if you use a content scanner diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/CryptoService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/CryptoService.kt index d6d1248de7..35f3ab3162 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/CryptoService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/CryptoService.kt @@ -26,6 +26,7 @@ import org.matrix.android.sdk.api.session.crypto.crosssigning.CrossSigningServic import org.matrix.android.sdk.api.session.crypto.crosssigning.DeviceTrustLevel import org.matrix.android.sdk.api.session.crypto.keysbackup.KeysBackupService import org.matrix.android.sdk.api.session.crypto.keyshare.GossipingRequestListener +import org.matrix.android.sdk.api.session.crypto.model.AuditTrail import org.matrix.android.sdk.api.session.crypto.model.CryptoDeviceInfo import org.matrix.android.sdk.api.session.crypto.model.DeviceInfo import org.matrix.android.sdk.api.session.crypto.model.DevicesListResponse @@ -35,8 +36,6 @@ import org.matrix.android.sdk.api.session.crypto.model.MXDeviceInfo import org.matrix.android.sdk.api.session.crypto.model.MXEncryptEventContentResult import org.matrix.android.sdk.api.session.crypto.model.MXEventDecryptionResult import org.matrix.android.sdk.api.session.crypto.model.MXUsersDevicesMap -import org.matrix.android.sdk.api.session.crypto.model.OutgoingRoomKeyRequest -import org.matrix.android.sdk.api.session.crypto.model.RoomKeyRequestBody import org.matrix.android.sdk.api.session.crypto.verification.VerificationService import org.matrix.android.sdk.api.session.events.model.Content import org.matrix.android.sdk.api.session.events.model.Event @@ -76,6 +75,15 @@ interface CryptoService { fun setGlobalBlacklistUnverifiedDevices(block: Boolean) + /** + * Enable or disable key gossiping. + * Default is true. + * If set to false this device won't send key_request nor will accept key forwarded + */ + fun enableKeyGossiping(enable: Boolean) + + fun isKeyGossipingEnabled(): Boolean + fun setRoomUnBlacklistUnverifiedDevices(roomId: String) fun getDeviceTrackingStatus(userId: String): Int @@ -94,8 +102,6 @@ interface CryptoService { fun reRequestRoomKeyForEvent(event: Event) - fun cancelRoomKeyRequest(requestBody: RoomKeyRequestBody) - fun addRoomKeysRequestListener(listener: GossipingRequestListener) fun removeRoomKeysRequestListener(listener: GossipingRequestListener) @@ -142,14 +148,20 @@ interface CryptoService { fun addNewSessionListener(newSessionListener: NewSessionListener) fun removeSessionListener(listener: NewSessionListener) - fun getOutgoingRoomKeyRequests(): List - fun getOutgoingRoomKeyRequestsPaged(): LiveData> + fun getOutgoingRoomKeyRequests(): List + fun getOutgoingRoomKeyRequestsPaged(): LiveData> fun getIncomingRoomKeyRequests(): List fun getIncomingRoomKeyRequestsPaged(): LiveData> - fun getGossipingEventsTrail(): LiveData> - fun getGossipingEvents(): List + /** + * Can be called by the app layer to accept a request manually. + * Use carefully as it is prone to social attacks. + */ + suspend fun manuallyAcceptRoomKeyRequest(request: IncomingRoomKeyRequest) + + fun getGossipingEventsTrail(): LiveData> + fun getGossipingEvents(): List // For testing shared session fun getSharedWithInfo(roomId: String?, sessionId: String): MXUsersDevicesMap diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/MXCryptoError.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/MXCryptoError.kt index 5ff4b54b11..0b5bbe3bbd 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/MXCryptoError.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/MXCryptoError.kt @@ -28,7 +28,7 @@ sealed class MXCryptoError : Throwable() { data class Base(val errorType: ErrorType, val technicalMessage: String, /** - * Describe the error with more details + * Describe the error with more details. */ val detailedErrorDescription: String? = null) : MXCryptoError() @@ -63,7 +63,7 @@ sealed class MXCryptoError : Throwable() { companion object { /** - * Resource for technicalMessage + * Resource for technicalMessage. */ const val UNABLE_TO_ENCRYPT_REASON = "Unable to encrypt %s" const val UNABLE_TO_DECRYPT_REASON = "Unable to decrypt %1\$s. Algorithm: %2\$s" diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/NewSessionListener.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/NewSessionListener.kt index 73cbf5fb78..d9e841a50f 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/NewSessionListener.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/NewSessionListener.kt @@ -17,7 +17,7 @@ package org.matrix.android.sdk.api.session.crypto /** - * This listener notifies on new Megolm sessions being created + * This listener notifies on new Megolm sessions being created. */ interface NewSessionListener { diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/OutgoingKeyRequest.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/OutgoingKeyRequest.kt new file mode 100644 index 0000000000..855f17a34f --- /dev/null +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/OutgoingKeyRequest.kt @@ -0,0 +1,58 @@ +/* + * Copyright (c) 2022 The Matrix.org Foundation C.I.C. + * + * 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 org.matrix.android.sdk.api.session.crypto + +import org.matrix.android.sdk.api.session.crypto.model.RoomKeyRequestBody +import org.matrix.android.sdk.api.session.events.model.content.WithHeldCode + +data class RequestReply( + val userId: String, + val fromDevice: String?, + val result: RequestResult +) + +sealed class RequestResult { + data class Success(val chainIndex: Int) : RequestResult() + data class Failure(val code: WithHeldCode) : RequestResult() +} + +data class OutgoingKeyRequest( + var requestBody: RoomKeyRequestBody?, + // recipients for the request map of users to list of deviceId + val recipients: Map>, + val fromIndex: Int, + // Unique id for this request. Used for both + // an id within the request for later pairing with a cancellation, and for + // the transaction id when sending the to_device messages to our local + val requestId: String, // current state of this request + val state: OutgoingRoomKeyRequestState, + val results: List +) { + /** + * Used only for log. + * + * @return the room id. + */ + val roomId = requestBody?.roomId + + /** + * Used only for log. + * + * @return the session id + */ + val sessionId = requestBody?.sessionId +} diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/OutgoingGossipingRequest.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/OutgoingRoomKeyRequestState.kt similarity index 55% rename from matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/OutgoingGossipingRequest.kt rename to matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/OutgoingRoomKeyRequestState.kt index 2438e01102..6e80bdc133 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/OutgoingGossipingRequest.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/OutgoingRoomKeyRequestState.kt @@ -1,5 +1,5 @@ /* - * Copyright 2020 The Matrix.org Foundation C.I.C. + * Copyright (c) 2022 The Matrix.org Foundation C.I.C. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,14 +14,20 @@ * limitations under the License. */ -package org.matrix.android.sdk.internal.crypto +package org.matrix.android.sdk.api.session.crypto -import org.matrix.android.sdk.api.session.crypto.model.OutgoingGossipingRequestState +enum class OutgoingRoomKeyRequestState { + UNSENT, + SENT, + SENT_THEN_CANCELED, + CANCELLATION_PENDING, + CANCELLATION_PENDING_AND_WILL_RESEND; -internal interface OutgoingGossipingRequest { - val recipients: Map> - val requestId: String - val state: OutgoingGossipingRequestState - // transaction id for the cancellation, if any - // var cancellationTxnId: String? + companion object { + fun pendingStates() = setOf( + UNSENT, + CANCELLATION_PENDING_AND_WILL_RESEND, + CANCELLATION_PENDING + ) + } } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/attachments/ElementToDecrypt.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/attachments/ElementToDecrypt.kt index de168ac6e5..2591703e0c 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/attachments/ElementToDecrypt.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/attachments/ElementToDecrypt.kt @@ -35,7 +35,7 @@ fun EncryptedFileInfo.toElementToDecrypt(): ElementToDecrypt? { } /** - * Represent data to decode an attachment + * Represent data to decode an attachment. */ @Parcelize data class ElementToDecrypt( diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/crosssigning/CrossSigningService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/crosssigning/CrossSigningService.kt index 46b131f613..5439389096 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/crosssigning/CrossSigningService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/crosssigning/CrossSigningService.kt @@ -66,7 +66,7 @@ interface CrossSigningService { fun markMyMasterKeyAsTrusted() /** - * Sign one of your devices and upload the signature + * Sign one of your devices and upload the signature. */ fun trustDevice(deviceId: String, callback: MatrixCallback) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/crosssigning/CryptoCrossSigningKey.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/crosssigning/CryptoCrossSigningKey.kt index 11996e673e..70ff76a4ef 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/crosssigning/CryptoCrossSigningKey.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/crosssigning/CryptoCrossSigningKey.kt @@ -93,7 +93,8 @@ data class CryptoCrossSigningKey( userId = userId, usages = listOf(usage.value), keys = mapOf("ed25519:$b64key" to b64key), - signatures = signMap) + signatures = signMap + ) } } } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/keysbackup/KeysBackupService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/keysbackup/KeysBackupService.kt index 9ff99f8dce..0d40490c3e 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/keysbackup/KeysBackupService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/keysbackup/KeysBackupService.kt @@ -23,7 +23,7 @@ import org.matrix.android.sdk.api.session.crypto.model.ImportRoomKeysResult interface KeysBackupService { /** - * Retrieve the current version of the backup from the homeserver + * Retrieve the current version of the backup from the homeserver. * * It can be different than keysBackupVersion. * @param callback Asynchronous callback @@ -40,12 +40,12 @@ interface KeysBackupService { callback: MatrixCallback) /** - * Facility method to get the total number of locally stored keys + * Facility method to get the total number of locally stored keys. */ fun getTotalNumbersOfKeys(): Int /** - * Facility method to get the number of backed up keys + * Facility method to get the number of backed up keys. */ fun getTotalNumbersOfBackedUpKeys(): Int @@ -68,7 +68,7 @@ interface KeysBackupService { callback: MatrixCallback) /** - * Return the current progress of the backup + * Return the current progress of the backup. */ fun getBackupProgress(progressListener: ProgressListener) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/keysbackup/KeysBackupStateListener.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/keysbackup/KeysBackupStateListener.kt index a6f4bd0ec7..32216fe3f5 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/keysbackup/KeysBackupStateListener.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/keysbackup/KeysBackupStateListener.kt @@ -19,7 +19,7 @@ package org.matrix.android.sdk.api.session.crypto.keysbackup interface KeysBackupStateListener { /** - * The keys backup state has changed + * The keys backup state has changed. * @param newState the new state */ fun onStateChange(newState: KeysBackupState) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/keysbackup/KeysBackupVersionTrustSignature.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/keysbackup/KeysBackupVersionTrustSignature.kt index 219a328cfd..7127c8d3f4 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/keysbackup/KeysBackupVersionTrustSignature.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/keysbackup/KeysBackupVersionTrustSignature.kt @@ -16,25 +16,35 @@ package org.matrix.android.sdk.api.session.crypto.keysbackup +import org.matrix.android.sdk.api.session.crypto.crosssigning.CryptoCrossSigningKey import org.matrix.android.sdk.api.session.crypto.model.CryptoDeviceInfo /** * A signature in a `KeysBackupVersionTrust` object. */ -data class KeysBackupVersionTrustSignature( - /** - * The id of the device that signed the backup version. - */ - val deviceId: String?, - /** - * The device that signed the backup version. - * Can be null if the device is not known. - */ - val device: CryptoDeviceInfo?, +sealed class KeysBackupVersionTrustSignature { - /** - * Flag to indicate the signature from this device is valid. - */ - val valid: Boolean, -) + data class DeviceSignature( + /** + * The id of the device that signed the backup version. + */ + val deviceId: String?, + + /** + * The device that signed the backup version. + * Can be null if the device is not known. + */ + val device: CryptoDeviceInfo?, + + /** + * Flag to indicate the signature from this device is valid. + */ + val valid: Boolean) : KeysBackupVersionTrustSignature() + + data class UserSignature( + val keyId: String?, + val cryptoCrossSigningKey: CryptoCrossSigningKey?, + val valid: Boolean + ) : KeysBackupVersionTrustSignature() +} diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/keysbackup/KeysVersionResult.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/keysbackup/KeysVersionResult.kt index f283a34e98..3d89bf9e2f 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/keysbackup/KeysVersionResult.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/keysbackup/KeysVersionResult.kt @@ -30,8 +30,8 @@ data class KeysVersionResult( override val algorithm: String, /** - * algorithm-dependent data, for "m.megolm_backup.v1.curve25519-aes-sha2" - * see [org.matrix.android.sdk.internal.crypto.keysbackup.MegolmBackupAuthData] + * algorithm-dependent data, for "m.megolm_backup.v1.curve25519-aes-sha2". + * @see [org.matrix.android.sdk.internal.crypto.keysbackup.MegolmBackupAuthData] */ @Json(name = "auth_data") override val authData: JsonDict, diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/keysbackup/RecoveryKey.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/keysbackup/RecoveryKey.kt index 85d6ef4365..9ad0bfc8b6 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/keysbackup/RecoveryKey.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/keysbackup/RecoveryKey.kt @@ -30,7 +30,7 @@ private const val CHAR_1 = 0x01.toByte() private const val RECOVERY_KEY_LENGTH = 2 + 32 + 1 /** - * Tell if the format of the recovery key is correct + * Tell if the format of the recovery key is correct. * * @param recoveryKey * @return true if the format of the recovery key is correct @@ -40,7 +40,7 @@ fun isValidRecoveryKey(recoveryKey: String?): Boolean { } /** - * Compute recovery key from curve25519 key + * Compute recovery key from curve25519 key. * * @param curve25519Key * @return the recovery key @@ -69,7 +69,7 @@ fun computeRecoveryKey(curve25519Key: ByteArray): String { } /** - * Please call [.isValidRecoveryKey] and ensure it returns true before calling this method + * Please call [.isValidRecoveryKey] and ensure it returns true before calling this method. * * @param recoveryKey the recovery key * @return curveKey, or null in case of error diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/keyshare/GossipingRequestListener.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/keyshare/GossipingRequestListener.kt index 3cd36c2ce8..cc160f8d85 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/keyshare/GossipingRequestListener.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/keyshare/GossipingRequestListener.kt @@ -16,12 +16,11 @@ package org.matrix.android.sdk.api.session.crypto.keyshare -import org.matrix.android.sdk.api.session.crypto.model.IncomingRequestCancellation import org.matrix.android.sdk.api.session.crypto.model.IncomingRoomKeyRequest -import org.matrix.android.sdk.api.session.crypto.model.IncomingSecretShareRequest +import org.matrix.android.sdk.api.session.crypto.model.SecretShareRequest /** - * Room keys events listener + * Room keys events listener. */ interface GossipingRequestListener { /** @@ -32,15 +31,15 @@ interface GossipingRequestListener { fun onRoomKeyRequest(request: IncomingRoomKeyRequest) /** - * Returns the secret value to be shared + * Returns the secret value to be shared. * @return true if is handled */ - fun onSecretShareRequest(request: IncomingSecretShareRequest): Boolean + fun onSecretShareRequest(request: SecretShareRequest): Boolean /** * A room key request cancellation has been received. * * @param request the cancellation request */ - fun onRoomKeyRequestCancellation(request: IncomingRequestCancellation) + fun onRequestCancelled(request: IncomingRoomKeyRequest) } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/model/AuditTrail.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/model/AuditTrail.kt new file mode 100644 index 0000000000..861f3bd30b --- /dev/null +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/model/AuditTrail.kt @@ -0,0 +1,85 @@ +/* + * Copyright 2022 The Matrix.org Foundation C.I.C. + * + * 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 org.matrix.android.sdk.api.session.crypto.model + +import com.squareup.moshi.JsonClass +import org.matrix.android.sdk.api.session.events.model.content.WithHeldCode + +enum class TrailType { + OutgoingKeyForward, + IncomingKeyForward, + OutgoingKeyWithheld, + IncomingKeyRequest, + Unknown +} + +interface AuditInfo { + val roomId: String + val sessionId: String + val senderKey: String + val alg: String + val userId: String + val deviceId: String +} + +@JsonClass(generateAdapter = true) +data class ForwardInfo( + override val roomId: String, + override val sessionId: String, + override val senderKey: String, + override val alg: String, + override val userId: String, + override val deviceId: String, + val chainIndex: Long? +) : AuditInfo + +object UnknownInfo : AuditInfo { + override val roomId: String = "" + override val sessionId: String = "" + override val senderKey: String = "" + override val alg: String = "" + override val userId: String = "" + override val deviceId: String = "" +} + +@JsonClass(generateAdapter = true) +data class WithheldInfo( + override val roomId: String, + override val sessionId: String, + override val senderKey: String, + override val alg: String, + val code: WithHeldCode, + override val userId: String, + override val deviceId: String +) : AuditInfo + +@JsonClass(generateAdapter = true) +data class IncomingKeyRequestInfo( + override val roomId: String, + override val sessionId: String, + override val senderKey: String, + override val alg: String, + override val userId: String, + override val deviceId: String, + val requestId: String +) : AuditInfo + +data class AuditTrail( + val ageLocalTs: Long, + val type: TrailType, + val info: AuditInfo +) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/model/DeviceInfo.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/model/DeviceInfo.kt index 221d0793d9..b144069b99 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/model/DeviceInfo.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/model/DeviceInfo.kt @@ -20,24 +20,24 @@ import com.squareup.moshi.JsonClass import org.matrix.android.sdk.api.interfaces.DatedObject /** - * This class describes the device information + * This class describes the device information. */ @JsonClass(generateAdapter = true) data class DeviceInfo( /** - * The owner user id (not documented and useless but the homeserver sent it. You should not need it) + * The owner user id (not documented and useless but the homeserver sent it. You should not need it). */ @Json(name = "user_id") val userId: String? = null, /** - * The device id + * The device id. */ @Json(name = "device_id") val deviceId: String? = null, /** - * The device display name + * The device display name. */ @Json(name = "display_name") val displayName: String? = null, @@ -49,7 +49,7 @@ data class DeviceInfo( val lastSeenTs: Long? = null, /** - * The last ip address + * The last ip address. */ @Json(name = "last_seen_ip") val lastSeenIp: String? = null diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/model/EncryptedFileInfo.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/model/EncryptedFileInfo.kt index 13ad1df476..fb64c6f338 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/model/EncryptedFileInfo.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/model/EncryptedFileInfo.kt @@ -20,7 +20,7 @@ import com.squareup.moshi.Json import com.squareup.moshi.JsonClass /** - * In Matrix specs: EncryptedFile + * In Matrix specs: EncryptedFile. */ @JsonClass(generateAdapter = true) data class EncryptedFileInfo( @@ -56,7 +56,7 @@ data class EncryptedFileInfo( val v: String? = null ) { /** - * Check what the spec tells us + * Check what the spec tells us. */ fun isValid(): Boolean { if (url.isNullOrBlank()) { diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/model/EncryptedFileKey.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/model/EncryptedFileKey.kt index 859c6ac43f..6308e3d615 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/model/EncryptedFileKey.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/model/EncryptedFileKey.kt @@ -52,7 +52,7 @@ data class EncryptedFileKey( val k: String? = null ) { /** - * Check what the spec tells us + * Check what the spec tells us. */ fun isValid(): Boolean { if (alg != "A256CTR") { diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/model/GossipingToDeviceObject.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/model/GossipingToDeviceObject.kt index 1922b2bcee..ac575332da 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/model/GossipingToDeviceObject.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/model/GossipingToDeviceObject.kt @@ -17,7 +17,7 @@ package org.matrix.android.sdk.api.session.crypto.model /** - * Interface representing an room key action request + * Interface representing an room key action request. * Note: this class cannot be abstract because of [org.matrix.androidsdk.core.JsonUtils.toRoomKeyShare] */ interface GossipingToDeviceObject : SendToDeviceObject { diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/model/IncomingRequestCancellation.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/model/IncomingRequestCancellation.kt deleted file mode 100755 index 74ca7304f7..0000000000 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/model/IncomingRequestCancellation.kt +++ /dev/null @@ -1,63 +0,0 @@ -/* - * Copyright 2020 The Matrix.org Foundation C.I.C. - * - * 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 org.matrix.android.sdk.api.session.crypto.model - -import org.matrix.android.sdk.api.session.events.model.Event -import org.matrix.android.sdk.api.session.events.model.toModel -import org.matrix.android.sdk.internal.crypto.IncomingShareRequestCommon -import org.matrix.android.sdk.internal.crypto.model.rest.ShareRequestCancellation - -/** - * IncomingRequestCancellation describes the incoming room key cancellation. - */ -data class IncomingRequestCancellation( - /** - * The user id - */ - override val userId: String? = null, - - /** - * The device id - */ - override val deviceId: String? = null, - - /** - * The request id - */ - override val requestId: String? = null, - override val localCreationTimestamp: Long? -) : IncomingShareRequestCommon { - companion object { - /** - * Factory - * - * @param event the event - */ - fun fromEvent(event: Event): IncomingRequestCancellation? { - return event.getClearContent() - .toModel() - ?.let { - IncomingRequestCancellation( - userId = event.senderId, - deviceId = it.requestingDeviceId, - requestId = it.requestId, - localCreationTimestamp = event.ageLocalTs ?: System.currentTimeMillis() - ) - } - } - } -} diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/model/IncomingRoomKeyRequest.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/model/IncomingRoomKeyRequest.kt index 45b0926d89..0c19d275cc 100755 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/model/IncomingRoomKeyRequest.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/model/IncomingRoomKeyRequest.kt @@ -16,67 +16,71 @@ package org.matrix.android.sdk.api.session.crypto.model -import org.matrix.android.sdk.api.session.events.model.Event -import org.matrix.android.sdk.api.session.events.model.toModel -import org.matrix.android.sdk.internal.crypto.IncomingShareRequestCommon +import org.matrix.android.sdk.internal.util.time.Clock /** * IncomingRoomKeyRequest class defines the incoming room keys request. */ data class IncomingRoomKeyRequest( /** - * The user id + * The user id. */ - override val userId: String? = null, + val userId: String? = null, /** - * The device id + * The device id. */ - override val deviceId: String? = null, + val deviceId: String? = null, /** - * The request id + * The request id. */ - override val requestId: String? = null, + val requestId: String? = null, /** - * The request body + * The request body. */ val requestBody: RoomKeyRequestBody? = null, - val state: GossipingRequestState = GossipingRequestState.NONE, - - /** - * The runnable to call to accept to share the keys - */ - @Transient - var share: Runnable? = null, - - /** - * The runnable to call to ignore the key share request. - */ - @Transient - var ignore: Runnable? = null, - override val localCreationTimestamp: Long? -) : IncomingShareRequestCommon { + val localCreationTimestamp: Long? +) { companion object { /** - * Factory + * Factory. * * @param event the event + * @param currentTimeMillis the current time in milliseconds */ - fun fromEvent(event: Event): IncomingRoomKeyRequest? { - return event.getClearContent() - .toModel() + fun fromEvent(trail: AuditTrail): IncomingRoomKeyRequest? { + return trail + .takeIf { it.type == TrailType.IncomingKeyRequest } + ?.let { + it.info as? IncomingKeyRequestInfo + } ?.let { IncomingRoomKeyRequest( - userId = event.senderId, - deviceId = it.requestingDeviceId, + userId = it.userId, + deviceId = it.deviceId, requestId = it.requestId, - requestBody = it.body ?: RoomKeyRequestBody(), - localCreationTimestamp = event.ageLocalTs ?: System.currentTimeMillis() + requestBody = RoomKeyRequestBody( + algorithm = it.alg, + roomId = it.roomId, + senderKey = it.senderKey, + sessionId = it.sessionId + ), + localCreationTimestamp = trail.ageLocalTs ) } } + + internal fun fromRestRequest(senderId: String, request: RoomKeyShareRequest, clock: Clock): IncomingRoomKeyRequest? { + return IncomingRoomKeyRequest( + userId = senderId, + deviceId = request.requestingDeviceId, + requestId = request.requestId, + requestBody = request.body, + localCreationTimestamp = clock.epochMillis() + ) + } } } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/model/IncomingSecretShareRequest.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/model/IncomingSecretShareRequest.kt deleted file mode 100755 index 5afffef1ae..0000000000 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/model/IncomingSecretShareRequest.kt +++ /dev/null @@ -1,82 +0,0 @@ -/* - * Copyright 2020 The Matrix.org Foundation C.I.C. - * - * 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 org.matrix.android.sdk.api.session.crypto.model - -import org.matrix.android.sdk.api.session.events.model.Event -import org.matrix.android.sdk.api.session.events.model.toModel -import org.matrix.android.sdk.internal.crypto.IncomingShareRequestCommon - -/** - * IncomingSecretShareRequest class defines the incoming secret keys request. - */ -data class IncomingSecretShareRequest( - /** - * The user id - */ - override val userId: String? = null, - - /** - * The device id - */ - override val deviceId: String? = null, - - /** - * The request id - */ - override val requestId: String? = null, - - /** - * The request body - */ - val secretName: String? = null, - - /** - * The runnable to call to accept to share the keys - */ - @Transient - var share: ((String) -> Unit)? = null, - - /** - * The runnable to call to ignore the key share request. - */ - @Transient - var ignore: Runnable? = null, - - override val localCreationTimestamp: Long? - -) : IncomingShareRequestCommon { - companion object { - /** - * Factory - * - * @param event the event - */ - fun fromEvent(event: Event): IncomingSecretShareRequest? { - return event.getClearContent() - .toModel() - ?.let { - IncomingSecretShareRequest( - userId = event.senderId, - deviceId = it.requestingDeviceId, - requestId = it.requestId, - secretName = it.secretName, - localCreationTimestamp = event.ageLocalTs ?: System.currentTimeMillis() - ) - } - } - } -} diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/model/MXDeviceInfo.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/model/MXDeviceInfo.kt index 286ab2b7d5..a5a581d240 100755 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/model/MXDeviceInfo.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/model/MXDeviceInfo.kt @@ -30,7 +30,7 @@ data class MXDeviceInfo( val deviceId: String, /** - * the user id + * the user id. */ @Json(name = "user_id") val userId: String, @@ -66,7 +66,7 @@ data class MXDeviceInfo( val verified: Int = DEVICE_VERIFICATION_UNKNOWN ) : Serializable { /** - * Tells if the device is unknown + * Tells if the device is unknown. * * @return true if the device is unknown */ diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/model/MXEncryptEventContentResult.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/model/MXEncryptEventContentResult.kt index 706e40a769..4cfcc01a26 100755 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/model/MXEncryptEventContentResult.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/model/MXEncryptEventContentResult.kt @@ -20,11 +20,11 @@ import org.matrix.android.sdk.api.session.events.model.Content data class MXEncryptEventContentResult( /** - * The encrypted event content + * The encrypted event content. */ val eventContent: Content, /** - * the event type + * The event type. */ val eventType: String ) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/model/MXUsersDevicesMap.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/model/MXUsersDevicesMap.kt index dc5567e908..744fe74d0d 100755 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/model/MXUsersDevicesMap.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/model/MXUsersDevicesMap.kt @@ -31,7 +31,7 @@ class MXUsersDevicesMap { get() = map.isEmpty() /** - * Provides the device ids list for a user id + * Provides the device ids list for a user id. * FIXME Should maybe return emptyList and not null, to avoid many !! in the code * * @param userId the user id @@ -44,7 +44,7 @@ class MXUsersDevicesMap { } /** - * Provides the object for a device id and a user Id + * Provides the object for a device id and a user Id. * * @param deviceId the device id * @param userId the object id @@ -57,7 +57,7 @@ class MXUsersDevicesMap { } /** - * Set an object for a dedicated user Id and device Id + * Set an object for a dedicated user Id and device Id. * * @param userId the user Id * @param deviceId the device id @@ -71,7 +71,7 @@ class MXUsersDevicesMap { } /** - * Defines the objects map for a user Id + * Defines the objects map for a user Id. * * @param objectsPerDevices the objects maps * @param userId the user id @@ -87,7 +87,7 @@ class MXUsersDevicesMap { } /** - * Removes objects for a dedicated user + * Removes objects for a dedicated user. * * @param userId the user id. */ @@ -98,14 +98,14 @@ class MXUsersDevicesMap { } /** - * Clear the internal dictionary + * Clear the internal dictionary. */ fun removeAllObjects() { map.clear() } /** - * Add entries from another MXUsersDevicesMap + * Add entries from another MXUsersDevicesMap. * * @param other the other one */ diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/model/OlmDecryptionResult.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/model/OlmDecryptionResult.kt index 9cf2bf75fb..a26f6606ed 100755 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/model/OlmDecryptionResult.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/model/OlmDecryptionResult.kt @@ -26,7 +26,7 @@ import org.matrix.android.sdk.api.util.JsonDict @JsonClass(generateAdapter = true) data class OlmDecryptionResult( /** - * The decrypted payload (with properties 'type', 'content') + * The decrypted payload (with properties 'type', 'content'). */ @Json(name = "payload") val payload: JsonDict? = null, diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/model/OutgoingRoomKeyRequest.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/model/OutgoingRoomKeyRequest.kt deleted file mode 100755 index 5f35cc908f..0000000000 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/model/OutgoingRoomKeyRequest.kt +++ /dev/null @@ -1,55 +0,0 @@ -/* - * Copyright 2020 The Matrix.org Foundation C.I.C. - * - * 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 org.matrix.android.sdk.api.session.crypto.model - -import com.squareup.moshi.JsonClass -import org.matrix.android.sdk.internal.crypto.OutgoingGossipingRequest - -/** - * Represents an outgoing room key request - */ -@JsonClass(generateAdapter = true) -data class OutgoingRoomKeyRequest( - // RequestBody - val requestBody: RoomKeyRequestBody?, - // list of recipients for the request - override val recipients: Map>, - // Unique id for this request. Used for both - // an id within the request for later pairing with a cancellation, and for - // the transaction id when sending the to_device messages to our local - override val requestId: String, // current state of this request - override val state: OutgoingGossipingRequestState - // transaction id for the cancellation, if any - // override var cancellationTxnId: String? = null -) : OutgoingGossipingRequest { - - /** - * Used only for log. - * - * @return the room id. - */ - val roomId: String? - get() = requestBody?.roomId - - /** - * Used only for log. - * - * @return the session id - */ - val sessionId: String? - get() = requestBody?.sessionId -} diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/model/RoomKeyRequestBody.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/model/RoomKeyRequestBody.kt index 15163248dc..8352949263 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/model/RoomKeyRequestBody.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/model/RoomKeyRequestBody.kt @@ -21,7 +21,7 @@ import com.squareup.moshi.JsonClass import org.matrix.android.sdk.internal.di.MoshiProvider /** - * Class representing an room key request body content + * Class representing an room key request body content. */ @JsonClass(generateAdapter = true) data class RoomKeyRequestBody( diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/model/RoomKeyShareRequest.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/model/RoomKeyShareRequest.kt index b6bb4c55af..adbe831a07 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/model/RoomKeyShareRequest.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/model/RoomKeyShareRequest.kt @@ -20,7 +20,7 @@ import com.squareup.moshi.Json import com.squareup.moshi.JsonClass /** - * Class representing a room key request content + * Class representing a room key request content. */ @JsonClass(generateAdapter = true) data class RoomKeyShareRequest( diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/model/SecretShareRequest.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/model/SecretShareRequest.kt index 6009077806..263a7b16e4 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/model/SecretShareRequest.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/model/SecretShareRequest.kt @@ -20,7 +20,7 @@ import com.squareup.moshi.Json import com.squareup.moshi.JsonClass /** - * Class representing a room key request content + * Class representing a room key request content. */ @JsonClass(generateAdapter = true) data class SecretShareRequest( diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/verification/PendingVerificationRequest.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/verification/PendingVerificationRequest.kt index be450b9d03..7db450e861 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/verification/PendingVerificationRequest.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/verification/PendingVerificationRequest.kt @@ -22,7 +22,7 @@ import org.matrix.android.sdk.internal.crypto.model.rest.VERIFICATION_METHOD_SAS import java.util.UUID /** - * Stores current pending verification requests + * Stores current pending verification requests. */ data class PendingVerificationRequest( val ageLocalTs: Long, @@ -45,7 +45,7 @@ data class PendingVerificationRequest( val isFinished: Boolean = isSuccessful || cancelConclusion != null /** - * SAS is supported if I support it and the other party support it + * SAS is supported if I support it and the other party support it. */ fun isSasSupported(): Boolean { return requestInfo?.methods?.contains(VERIFICATION_METHOD_SAS).orFalse() && @@ -53,7 +53,7 @@ data class PendingVerificationRequest( } /** - * Other can show QR code if I can scan QR code and other can show QR code + * Other can show QR code if I can scan QR code and other can show QR code. */ fun otherCanShowQrCode(): Boolean { return if (isIncoming) { @@ -66,7 +66,7 @@ data class PendingVerificationRequest( } /** - * Other can scan QR code if I can show QR code and other can scan QR code + * Other can scan QR code if I can show QR code and other can scan QR code. */ fun otherCanScanQrCode(): Boolean { return if (isIncoming) { diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/verification/QrCodeVerificationTransaction.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/verification/QrCodeVerificationTransaction.kt index 37855099be..06bac4109b 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/verification/QrCodeVerificationTransaction.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/verification/QrCodeVerificationTransaction.kt @@ -19,22 +19,22 @@ package org.matrix.android.sdk.api.session.crypto.verification interface QrCodeVerificationTransaction : VerificationTransaction { /** - * To use to display a qr code, for the other user to scan it + * To use to display a qr code, for the other user to scan it. */ val qrCodeText: String? /** - * Call when you have scan the other user QR code + * Call when you have scan the other user QR code. */ fun userHasScannedOtherQrCode(otherQrCodeText: String) /** - * Call when you confirm that other user has scanned your QR code + * Call when you confirm that other user has scanned your QR code. */ fun otherUserScannedMyQrCode() /** - * Call when you do not confirm that other user has scanned your QR code + * Call when you do not confirm that other user has scanned your QR code. */ fun otherUserDidNotScannedMyQrCode() } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/verification/SasVerificationTransaction.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/verification/SasVerificationTransaction.kt index da546be68f..095b4208f8 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/verification/SasVerificationTransaction.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/verification/SasVerificationTransaction.kt @@ -28,7 +28,7 @@ interface SasVerificationTransaction : VerificationTransaction { /** * To be called by the client when the user has verified that - * both short codes do match + * both short codes do match. */ fun userHasVerifiedShortCode() diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/verification/VerificationMethod.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/verification/VerificationMethod.kt index 4efec93a34..f2de2c4b47 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/verification/VerificationMethod.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/verification/VerificationMethod.kt @@ -17,7 +17,7 @@ package org.matrix.android.sdk.api.session.crypto.verification /** - * Verification methods + * Verification methods. */ enum class VerificationMethod { // Use it when your application supports the SAS verification method diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/verification/VerificationService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/verification/VerificationService.kt index b9d0c0ad2c..321ec73094 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/verification/VerificationService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/verification/VerificationService.kt @@ -34,7 +34,7 @@ interface VerificationService { fun removeListener(listener: Listener) /** - * Mark this device as verified manually + * Mark this device as verified manually. */ fun markedLocallyAsManuallyVerified(userId: String, deviceID: String) @@ -52,7 +52,7 @@ interface VerificationService { transactionId: String?): String? /** - * Request key verification with another user via room events (instead of the to-device API) + * Request key verification with another user via room events (instead of the to-device API). */ fun requestKeyVerificationInDMs(methods: List, otherUserId: String, @@ -81,7 +81,7 @@ interface VerificationService { otherDeviceId: String): String /** - * Returns false if the request is unknown + * Returns false if the request is unknown. */ fun readyPendingVerificationInDMs(methods: List, otherUserId: String, @@ -89,7 +89,7 @@ interface VerificationService { transactionId: String): Boolean /** - * Returns false if the request is unknown + * Returns false if the request is unknown. */ fun readyPendingVerification(methods: List, otherUserId: String, @@ -129,11 +129,10 @@ interface VerificationService { private const val TEN_MINUTES_IN_MILLIS = 10 * 60 * 1000 private const val FIVE_MINUTES_IN_MILLIS = 5 * 60 * 1000 - fun isValidRequest(age: Long?): Boolean { + fun isValidRequest(age: Long?, currentTimeMillis: Long): Boolean { if (age == null) return false - val now = System.currentTimeMillis() - val tooInThePast = now - TEN_MINUTES_IN_MILLIS - val tooInTheFuture = now + FIVE_MINUTES_IN_MILLIS + val tooInThePast = currentTimeMillis - TEN_MINUTES_IN_MILLIS + val tooInTheFuture = currentTimeMillis + FIVE_MINUTES_IN_MILLIS return age in tooInThePast..tooInTheFuture } } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/verification/VerificationTransaction.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/verification/VerificationTransaction.kt index 4d35bc44ac..b68a82c604 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/verification/VerificationTransaction.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/verification/VerificationTransaction.kt @@ -28,7 +28,7 @@ interface VerificationTransaction { val isIncoming: Boolean /** - * User wants to cancel the transaction + * User wants to cancel the transaction. */ fun cancel() diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/events/model/Event.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/events/model/Event.kt index 1ce51a2bde..16bdbd3432 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/events/model/Event.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/events/model/Event.kt @@ -62,7 +62,7 @@ inline fun Content?.toModel(catchError: Boolean = true): T? { } /** - * This methods is a facility method to map a model to a json Content + * This methods is a facility method to map a model to a json Content. */ @Suppress("UNCHECKED_CAST") inline fun T.toContent(): Content { @@ -123,7 +123,7 @@ data class Event( var ageLocalTs: Long? = null /** - * Copy all fields, including transient fields + * Copy all fields, including transient fields. */ fun copyAll(): Event { return copy().also { @@ -227,14 +227,14 @@ data class Event( } /** - * Determines whether or not current event has mentioned the user + * Determines whether or not current event has mentioned the user. */ fun isUserMentioned(userId: String): Boolean { return getDecryptedValue("formatted_body")?.contains(userId) ?: false } /** - * Decrypt the message, or return the pure payload value if there is no encryption + * Decrypt the message, or return the pure payload value if there is no encryption. */ private fun getDecryptedValue(key: String = "body"): String? { return if (isEncrypted()) { @@ -247,7 +247,7 @@ data class Event( } /** - * Tells if the event is redacted + * Tells if the event is redacted. */ fun isRedacted() = unsignedData?.redactedEvent != null @@ -305,7 +305,7 @@ data class Event( /** * Return the value of "content.msgtype", if the Event type is "m.room.message" - * and if the content has it, and if it is a String + * and if the content has it, and if it is a String. */ fun Event.getMsgType(): String? { if (getClearType() != EventType.MESSAGE) return null @@ -386,13 +386,13 @@ fun Event.getRelationContent(): RelationDefaultContent? { } /** - * Returns the poll question or null otherwise + * Returns the poll question or null otherwise. */ fun Event.getPollQuestion(): String? = getPollContent()?.getBestPollCreationInfo()?.question?.getBestQuestion() /** - * Returns the relation content for a specific type or null otherwise + * Returns the relation content for a specific type or null otherwise. */ fun Event.getRelationContentForType(type: String): RelationDefaultContent? = getRelationContent()?.takeIf { it.type == type } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/events/model/RelationType.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/events/model/RelationType.kt index 74dc74b294..3db9262c5b 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/events/model/RelationType.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/events/model/RelationType.kt @@ -16,7 +16,7 @@ package org.matrix.android.sdk.api.session.events.model /** - * Constants defining known event relation types from Matrix specifications + * Constants defining known event relation types from Matrix specifications. */ object RelationType { /** Lets you define an event which annotates an existing event.*/ diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/events/model/content/EncryptedEventContent.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/events/model/content/EncryptedEventContent.kt index 4f39bb61e1..b8388ea002 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/events/model/content/EncryptedEventContent.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/events/model/content/EncryptedEventContent.kt @@ -20,37 +20,37 @@ import com.squareup.moshi.JsonClass import org.matrix.android.sdk.api.session.room.model.relation.RelationDefaultContent /** - * Class representing an encrypted event content + * Class representing an encrypted event content. */ @JsonClass(generateAdapter = true) data class EncryptedEventContent( /** - * the used algorithm + * The used algorithm. */ @Json(name = "algorithm") val algorithm: String? = null, /** - * The encrypted event + * The encrypted event. */ @Json(name = "ciphertext") val ciphertext: String? = null, /** - * The device id + * The device id. */ @Json(name = "device_id") val deviceId: String? = null, /** - * the sender key + * The sender key. */ @Json(name = "sender_key") val senderKey: String? = null, /** - * The session id + * The session id. */ @Json(name = "session_id") val sessionId: String? = null, diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/events/model/content/EncryptionEventContent.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/events/model/content/EncryptionEventContent.kt index 103293ba83..321afd186d 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/events/model/content/EncryptionEventContent.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/events/model/content/EncryptionEventContent.kt @@ -19,7 +19,7 @@ import com.squareup.moshi.Json import com.squareup.moshi.JsonClass /** - * Class representing an encrypted event content + * Class representing an encrypted event content. */ @JsonClass(generateAdapter = true) data class EncryptionEventContent( diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/events/model/content/OlmEventContent.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/events/model/content/OlmEventContent.kt index b972dd20bb..65e8128182 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/events/model/content/OlmEventContent.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/events/model/content/OlmEventContent.kt @@ -19,7 +19,7 @@ import com.squareup.moshi.Json import com.squareup.moshi.JsonClass /** - * Class representing an encrypted event content + * Class representing an encrypted event content. */ @JsonClass(generateAdapter = true) data class OlmEventContent( @@ -30,7 +30,7 @@ data class OlmEventContent( val ciphertext: Map? = null, /** - * the sender key + * the sender key. */ @Json(name = "sender_key") val senderKey: String? = null diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/events/model/content/OlmPayloadContent.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/events/model/content/OlmPayloadContent.kt index 6060ab5c4b..c3d8a5a800 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/events/model/content/OlmPayloadContent.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/events/model/content/OlmPayloadContent.kt @@ -20,36 +20,36 @@ import com.squareup.moshi.JsonClass import org.matrix.android.sdk.internal.di.MoshiProvider /** - * Class representing the OLM payload content + * Class representing the OLM payload content. */ @JsonClass(generateAdapter = true) data class OlmPayloadContent( /** - * The room id + * The room id. */ @Json(name = "room_id") val roomId: String? = null, /** - * The sender + * The sender. */ @Json(name = "sender") val sender: String? = null, /** - * The recipient + * The recipient. */ @Json(name = "recipient") val recipient: String? = null, /** - * the recipient keys + * The recipient keys. */ @Json(name = "recipient_keys") val recipientKeys: Map? = null, /** - * The keys + * The keys. */ @Json(name = "keys") val keys: Map? = null diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/events/model/content/RoomKeyContent.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/events/model/content/RoomKeyContent.kt index 43a47b818f..0830a566ab 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/events/model/content/RoomKeyContent.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/events/model/content/RoomKeyContent.kt @@ -19,7 +19,7 @@ import com.squareup.moshi.Json import com.squareup.moshi.JsonClass /** - * Class representing an sharekey content + * Class representing an sharekey content. */ @JsonClass(generateAdapter = true) data class RoomKeyContent( diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/events/model/content/RoomKeyWithHeldContent.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/events/model/content/RoomKeyWithHeldContent.kt index a577daf9e4..d58c3614a7 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/events/model/content/RoomKeyWithHeldContent.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/events/model/content/RoomKeyWithHeldContent.kt @@ -19,7 +19,7 @@ import com.squareup.moshi.Json import com.squareup.moshi.JsonClass /** - * Class representing an sharekey content + * Class representing an sharekey content. */ @JsonClass(generateAdapter = true) data class RoomKeyWithHeldContent( @@ -52,7 +52,13 @@ data class RoomKeyWithHeldContent( /** * A human-readable reason for why the key was not sent. The receiving client should only use this string if it does not understand the code. */ - @Json(name = "reason") val reason: String? = null + @Json(name = "reason") val reason: String? = null, + + /** + * the device ID of the device sending the m.room_key.withheld message + * MSC3735. + */ + @Json(name = "from_device") val fromDevice: String? = null ) { val code: WithHeldCode? @@ -63,23 +69,23 @@ data class RoomKeyWithHeldContent( enum class WithHeldCode(val value: String) { /** - * the user/device was blacklisted + * the user/device was blacklisted. */ BLACKLISTED("m.blacklisted"), /** - * the user/devices is unverified + * the user/devices is unverified. */ UNVERIFIED("m.unverified"), /** * the user/device is not allowed have the key. For example, this would usually be sent in response - * to a key request if the user was not in the room when the message was sent + * to a key request if the user was not in the room when the message was sent. */ UNAUTHORISED("m.unauthorised"), /** - * Sent in reply to a key request if the device that the key is requested from does not have the requested key + * Sent in reply to a key request if the device that the key is requested from does not have the requested key. */ UNAVAILABLE("m.unavailable"), diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/events/model/content/SecretSendEventContent.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/events/model/content/SecretSendEventContent.kt index 5099aba403..be9d9d638c 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/events/model/content/SecretSendEventContent.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/events/model/content/SecretSendEventContent.kt @@ -19,7 +19,7 @@ import com.squareup.moshi.Json import com.squareup.moshi.JsonClass /** - * Class representing an encrypted event content + * Class representing an encrypted event content. */ @JsonClass(generateAdapter = true) data class SecretSendEventContent( diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/file/FileService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/file/FileService.kt index 72f8019ada..84a9990826 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/file/FileService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/file/FileService.kt @@ -45,9 +45,9 @@ interface FileService { * Result will be a decrypted file, stored in the cache folder. url parameter will be used to create unique filename to avoid name collision. */ suspend fun downloadFile(fileName: String, - mimeType: String?, - url: String?, - elementToDecrypt: ElementToDecrypt?): File + mimeType: String?, + url: String?, + elementToDecrypt: ElementToDecrypt?): File suspend fun downloadFile(messageContent: MessageWithAttachmentContent): File = downloadFile( @@ -68,11 +68,12 @@ interface FileService { mxcUrl = messageContent.getFileUrl(), fileName = messageContent.getFileName(), mimeType = messageContent.mimeType, - elementToDecrypt = messageContent.encryptedFileInfo?.toElementToDecrypt()) + elementToDecrypt = messageContent.encryptedFileInfo?.toElementToDecrypt() + ) /** * Use this URI and pass it to intent using flag Intent.FLAG_GRANT_READ_URI_PERMISSION - * (if not other app won't be able to access it) + * (if not other app won't be able to access it). */ fun getTemporarySharableURI(mxcUrl: String?, fileName: String, @@ -105,17 +106,17 @@ interface FileService { ) /** - * Clears all the files downloaded by the service, including decrypted files + * Clears all the files downloaded by the service, including decrypted files. */ fun clearCache() /** - * Clears all the decrypted files by the service + * Clears all the decrypted files by the service. */ fun clearDecryptedCache() /** - * Get size of cached files + * Get size of cached files. */ fun getCacheSize(): Long } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/file/MatrixSDKFileProvider.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/file/MatrixSDKFileProvider.kt index ee1550d1db..113bf9333f 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/file/MatrixSDKFileProvider.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/file/MatrixSDKFileProvider.kt @@ -21,7 +21,7 @@ import androidx.core.content.FileProvider /** * We have to declare our own file provider to avoid collision with apps using the sdk - * and having their own + * and having their own. */ class MatrixSDKFileProvider : FileProvider() { override fun getType(uri: Uri): String? { diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/group/GroupService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/group/GroupService.kt index a96466603c..1968af222a 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/group/GroupService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/group/GroupService.kt @@ -23,16 +23,15 @@ import org.matrix.android.sdk.api.session.group.model.GroupSummary * This interface defines methods to get groups. It's implemented at the session level. */ interface GroupService { - /** - * Get a group from a groupId + * Get a group from a groupId. * @param groupId the groupId to look for. * @return the group with groupId or null */ fun getGroup(groupId: String): Group? /** - * Get a groupSummary from a groupId + * Get a groupSummary from a groupId. * @param groupId the groupId to look for. * @return the groupSummary with groupId or null */ diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/group/GroupSummaryQueryParams.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/group/GroupSummaryQueryParams.kt index 0761a22c77..5104b3ee53 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/group/GroupSummaryQueryParams.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/group/GroupSummaryQueryParams.kt @@ -24,7 +24,7 @@ fun groupSummaryQueryParams(init: (GroupSummaryQueryParams.Builder.() -> Unit) = } /** - * This class can be used to filter group summaries + * This class can be used to filter group summaries. */ data class GroupSummaryQueryParams( val displayName: QueryStringValue, diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/homeserver/HomeServerCapabilities.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/homeserver/HomeServerCapabilities.kt index 9db3876b74..5b06fdacae 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/homeserver/HomeServerCapabilities.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/homeserver/HomeServerCapabilities.kt @@ -34,27 +34,27 @@ data class HomeServerCapabilities( */ val canChange3pid: Boolean = true, /** - * Max size of file which can be uploaded to the homeserver in bytes. [MAX_UPLOAD_FILE_SIZE_UNKNOWN] if unknown or not retrieved yet + * Max size of file which can be uploaded to the homeserver in bytes. [MAX_UPLOAD_FILE_SIZE_UNKNOWN] if unknown or not retrieved yet. */ val maxUploadFileSize: Long = MAX_UPLOAD_FILE_SIZE_UNKNOWN, /** - * Last version identity server and binding supported + * Last version identity server and binding supported. */ val lastVersionIdentityServerSupported: Boolean = false, /** - * Default identity server url, provided in Wellknown + * Default identity server url, provided in Wellknown. */ val defaultIdentityServerUrl: String? = null, /** - * Room versions supported by the server + * Room versions supported by the server. * This capability describes the default and available room versions a server supports, and at what level of stability. * Clients should make use of this capability to determine if users need to be encouraged to upgrade their rooms. */ val roomVersions: RoomVersionCapabilities? = null, /** - * True if the home server support threading + * True if the home server support threading. */ - var canUseThreading: Boolean = false + val canUseThreading: Boolean = false ) { enum class RoomCapabilitySupport { diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/homeserver/HomeServerCapabilitiesService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/homeserver/HomeServerCapabilitiesService.kt index f12cbcd6db..9d2c48e194 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/homeserver/HomeServerCapabilitiesService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/homeserver/HomeServerCapabilitiesService.kt @@ -22,12 +22,12 @@ package org.matrix.android.sdk.api.session.homeserver interface HomeServerCapabilitiesService { /** - * Force a refresh of the stored data + * Force a refresh of the stored data. */ suspend fun refreshHomeServerCapabilities() /** - * Get the HomeServer capabilities + * Get the HomeServer capabilities. */ fun getHomeServerCapabilities(): HomeServerCapabilities } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/identity/IdentityService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/identity/IdentityService.kt index fdcb30a5c8..c03b42e6c8 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/identity/IdentityService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/identity/IdentityService.kt @@ -19,13 +19,13 @@ package org.matrix.android.sdk.api.session.identity import org.matrix.android.sdk.api.session.identity.model.SignInvitationResult /** - * Provides access to the identity server configuration and services identity server can provide + * Provides access to the identity server configuration and services identity server can provide. */ interface IdentityService { /** * Return the default identity server of the user, which may have been provided at login time by the homeserver, - * or by the Well-known setup of the homeserver - * It may be different from the current configured identity server + * or by the Well-known setup of the homeserver. + * It may be different from the current configured identity server. */ fun getDefaultIdentityServer(): String? @@ -35,9 +35,9 @@ interface IdentityService { fun getCurrentIdentityServerUrl(): String? /** - * Check if the identity server is valid - * See https://matrix.org/docs/spec/identity_service/latest#status-check - * Matrix Android SDK2 only supports identity server API v2 + * Check if the identity server is valid. + * See https://matrix.org/docs/spec/identity_service/latest#status-check. + * Matrix Android SDK2 only supports identity server API v2. */ suspend fun isValidIdentityServer(url: String) @@ -52,12 +52,12 @@ interface IdentityService { suspend fun setNewIdentityServer(url: String): String /** - * Disconnect (logout) from the current identity server + * Disconnect (logout) from the current identity server. */ suspend fun disconnect() /** - * This will ask the identity server to send an email or an SMS to let the user confirm he owns the ThreePid + * This will ask the identity server to send an email or an SMS to let the user confirm he owns the ThreePid. */ suspend fun startBindThreePid(threePid: ThreePid) @@ -67,32 +67,32 @@ interface IdentityService { suspend fun cancelBindThreePid(threePid: ThreePid) /** - * This will ask the identity server to send an new email or a new SMS to let the user confirm he owns the ThreePid + * This will ask the identity server to send an new email or a new SMS to let the user confirm he owns the ThreePid. */ suspend fun sendAgainValidationCode(threePid: ThreePid) /** - * Submit the code that the identity server has sent to the user (in email or SMS) + * Submit the code that the identity server has sent to the user (in email or SMS). * Once successful, you will have to call [finalizeBindThreePid] * @param code the code sent to the user */ suspend fun submitValidationToken(threePid: ThreePid, code: String) /** - * This will perform the actual association of ThreePid and Matrix account + * This will perform the actual association of ThreePid and Matrix account. */ suspend fun finalizeBindThreePid(threePid: ThreePid) /** - * Unbind a threePid - * The request will actually be done on the homeserver + * Unbind a threePid. + * The request will actually be done on the homeserver. */ suspend fun unbindThreePid(threePid: ThreePid) /** - * Search MatrixId of users providing email and phone numbers - * Note the the user consent has to be set to true, or it will throw a UserConsentNotProvided failure - * Application has to explicitly ask for the user consent, and the answer can be stored using [setUserConsent] + * Search MatrixId of users providing email and phone numbers. + * Note the the user consent has to be set to true, or it will throw a UserConsentNotProvided failure. + * Application has to explicitly ask for the user consent, and the answer can be stored using [setUserConsent]. * Please see https://support.google.com/googleplay/android-developer/answer/9888076?hl=en for more details. */ suspend fun lookUp(threePids: List): List @@ -115,8 +115,8 @@ interface IdentityService { fun setUserConsent(newValue: Boolean) /** - * Get the status of the current user's threePid - * A lookup will be performed, but also pending binding state will be restored + * Get the status of the current user's threePid. + * A lookup will be performed, but also pending binding state will be restored. * * @param threePids the list of threePid the user owns (retrieved form the homeserver) * @return a map of ThreePid -> SharedState @@ -126,7 +126,7 @@ interface IdentityService { /** * When one performs a 3pid invite and the third party identifier is unknown, the home server * will store the invitation in the Identity server and store some information in the room state membership event. - * The email invite will contains the token and secret that can be used to claim the stored invitation + * The email invite will contains the token and secret that can be used to claim the stored invitation. * * To aid clients who may not be able to perform crypto themselves, * the identity server offers some crypto functionality to help in accepting invitations. diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/identity/model/SignInvitationResult.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/identity/model/SignInvitationResult.kt index b1662b9cf8..28d9d154f3 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/identity/model/SignInvitationResult.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/identity/model/SignInvitationResult.kt @@ -33,7 +33,7 @@ data class SignInvitationResult( */ val signatures: Map, /** - * The token for the invitation + * The token for the invitation. */ val token: String ) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/initsync/SyncStatusService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/initsync/SyncStatusService.kt index 267436916e..7006e11751 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/initsync/SyncStatusService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/initsync/SyncStatusService.kt @@ -23,7 +23,7 @@ interface SyncStatusService { sealed class Status { /** - * For initial sync + * For initial sync. */ abstract class InitialSyncStatus : Status() @@ -34,7 +34,7 @@ interface SyncStatusService { ) : InitialSyncStatus() /** - * For incremental sync + * For incremental sync. */ abstract class IncrementalSyncStatus : Status() @@ -43,6 +43,7 @@ interface SyncStatusService { val rooms: Int, val toDevice: Int ) : IncrementalSyncStatus() + object IncrementalSyncError : IncrementalSyncStatus() object IncrementalSyncDone : IncrementalSyncStatus() } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/integrationmanager/IntegrationManagerConfig.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/integrationmanager/IntegrationManagerConfig.kt index 069ed7427c..b04b31af3b 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/integrationmanager/IntegrationManagerConfig.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/integrationmanager/IntegrationManagerConfig.kt @@ -30,17 +30,17 @@ data class IntegrationManagerConfig( */ enum class Kind { /** - * Defined in UserAccountData + * Defined in UserAccountData. */ ACCOUNT, /** - * Defined in Wellknown + * Defined in Wellknown. */ HOMESERVER, /** - * Fallback value, hardcoded by the SDK + * Fallback value, hardcoded by the SDK. */ DEFAULT } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/media/MediaService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/media/MediaService.kt index 3b3ef57d73..2e53e67b0c 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/media/MediaService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/media/MediaService.kt @@ -36,7 +36,7 @@ interface MediaService { suspend fun getRawPreviewUrl(url: String, timestamp: Long?): JsonDict /** - * Get Url Preview data from the homeserver, or from cache, depending on the cache strategy + * Get Url Preview data from the homeserver, or from cache, depending on the cache strategy. * @param url The url to get the preview data from * @param timestamp The optional timestamp. Note that this parameter is not taken into account * if the data is already in cache and the cache strategy allow to use it @@ -45,7 +45,7 @@ interface MediaService { suspend fun getPreviewUrl(url: String, timestamp: Long?, cacheStrategy: CacheStrategy): PreviewUrlData /** - * Clear the cache of all retrieved UrlPreview data + * Clear the cache of all retrieved UrlPreview data. */ suspend fun clearCache() } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/media/PreviewUrlData.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/media/PreviewUrlData.kt index bfba43a82d..b142ad9754 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/media/PreviewUrlData.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/media/PreviewUrlData.kt @@ -17,7 +17,7 @@ package org.matrix.android.sdk.api.session.media /** - * Facility data class to get the common field of a PreviewUrl response form the server + * Facility data class to get the common field of a PreviewUrl response form the server. * * Example of return data for the url `https://matrix.org`: *
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/permalinks/MatrixLinkify.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/permalinks/MatrixLinkify.kt
index 3e27da0c41..c5d919407a 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/permalinks/MatrixLinkify.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/permalinks/MatrixLinkify.kt
@@ -55,7 +55,7 @@ object MatrixLinkify {
                             MatrixPatterns.isRoomId(url) ||
                             MatrixPatterns.isGroupId(url) ||
                             MatrixPatterns.isEventId(url)) {
-                        url = PermalinkService.MATRIX_TO_URL_BASE  + url
+                        url = PermalinkService.MATRIX_TO_URL_BASE + url
                     }
                     val span = MatrixPermalinkSpan(url, callback)
                     spannable.setSpan(span, startPos, endPos, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE)
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/permalinks/PermalinkData.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/permalinks/PermalinkData.kt
index 85291cf0f6..e8d9c89b54 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/permalinks/PermalinkData.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/permalinks/PermalinkData.kt
@@ -33,23 +33,23 @@ sealed class PermalinkData {
             val viaParameters: List
     ) : PermalinkData()
 
-    /**
+    /*
      * &room_name=Team2
-        &room_avatar_url=mxc:
-         &inviter_name=bob
+     * &room_avatar_url=mxc:
+     * &inviter_name=bob
      */
     @Parcelize
     data class RoomEmailInviteLink(
-        val roomId: String,
-        val email: String,
-        val signUrl: String,
-        val roomName: String?,
-        val roomAvatarUrl: String?,
-        val inviterName: String?,
-        val identityServer: String,
-        val token: String,
-        val privateKey: String,
-        val roomType: String?
+            val roomId: String,
+            val email: String,
+            val signUrl: String,
+            val roomName: String?,
+            val roomAvatarUrl: String?,
+            val inviterName: String?,
+            val identityServer: String,
+            val token: String,
+            val privateKey: String,
+            val roomType: String?
     ) : PermalinkData(), Parcelable
 
     data class UserLink(val userId: String) : PermalinkData()
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/permalinks/PermalinkParser.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/permalinks/PermalinkParser.kt
index edb748c76e..9d078dc4b2 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/permalinks/PermalinkParser.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/permalinks/PermalinkParser.kt
@@ -23,7 +23,7 @@ import timber.log.Timber
 import java.net.URLDecoder
 
 /**
- * This class turns a uri to a [PermalinkData]
+ * This class turns a uri to a [PermalinkData].
  * element-based domains (e.g. https://app.element.io/#/user/@chagai95:matrix.org) permalinks
  * or matrix.to permalinks (e.g. https://matrix.to/#/@chagai95:matrix.org)
  * or client permalinks (e.g. user/@chagai95:matrix.org)
@@ -31,7 +31,7 @@ import java.net.URLDecoder
 object PermalinkParser {
 
     /**
-     * Turns a uri string to a [PermalinkData]
+     * Turns a uri string to a [PermalinkData].
      */
     fun parse(uriString: String): PermalinkData {
         val uri = Uri.parse(uriString)
@@ -39,7 +39,7 @@ object PermalinkParser {
     }
 
     /**
-     * Turns a uri to a [PermalinkData]
+     * Turns a uri to a [PermalinkData].
      * https://github.com/matrix-org/matrix-doc/blob/master/proposals/1704-matrix.to-permalinks.md
      */
     fun parse(uri: Uri): PermalinkData {
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/permalinks/PermalinkService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/permalinks/PermalinkService.kt
index c139da813a..b49b80df09 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/permalinks/PermalinkService.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/permalinks/PermalinkService.kt
@@ -57,7 +57,7 @@ interface PermalinkService {
     fun createPermalink(id: String, forceMatrixTo: Boolean = false): String?
 
     /**
-     * Creates a permalink for a roomId, including the via parameters
+     * Creates a permalink for a roomId, including the via parameters.
      *
      * @param roomId the room id
      * @param forceMatrixTo whether we should force using matrix.to base URL
@@ -79,7 +79,7 @@ interface PermalinkService {
     fun createPermalink(roomId: String, eventId: String, forceMatrixTo: Boolean = false): String
 
     /**
-     * Extract the linked id from the universal link
+     * Extract the linked id from the universal link.
      *
      * @param url the universal link, Ex: "https://matrix.to/#/@benoit:matrix.org"
      * @return the id from the url, ex: "@benoit:matrix.org", or null if the url is not a permalink
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/presence/PresenceService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/presence/PresenceService.kt
index 82a81f4b64..901e7ec3dd 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/presence/PresenceService.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/presence/PresenceService.kt
@@ -25,7 +25,7 @@ import org.matrix.android.sdk.api.session.presence.model.UserPresence
  */
 interface PresenceService {
     /**
-     * Update the presence status for the current user
+     * Update the presence status for the current user.
      * @param presence the new presence state
      * @param statusMsg the status message to attach to this state
      */
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/presence/model/PresenceEnum.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/presence/model/PresenceEnum.kt
index 6d9994ef1c..c678e2a706 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/presence/model/PresenceEnum.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/presence/model/PresenceEnum.kt
@@ -28,7 +28,10 @@ enum class PresenceEnum(val value: String) {
     OFFLINE("offline"),
 
     @Json(name = "unavailable")
-    UNAVAILABLE("unavailable");
+    UNAVAILABLE("unavailable"),
+
+    @Json(name = "org.matrix.msc3026.busy")
+    BUSY("busy");
 
     companion object {
         fun from(s: String): PresenceEnum? = values().find { it.value == s }
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/profile/ProfileService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/profile/ProfileService.kt
index d2c677bb31..095f2ef7c2 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/profile/ProfileService.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/profile/ProfileService.kt
@@ -36,21 +36,21 @@ interface ProfileService {
     }
 
     /**
-     * Return the current display name for this user
+     * Return the current display name for this user.
      * @param userId the userId param to look for
      *
      */
     suspend fun getDisplayName(userId: String): Optional
 
     /**
-     * Update the display name for this user
+     * Update the display name for this user.
      * @param userId the userId to update the display name of
      * @param newDisplayName the new display name of the user
      */
     suspend fun setDisplayName(userId: String, newDisplayName: String)
 
     /**
-     * Update the avatar for this user
+     * Update the avatar for this user.
      * @param userId the userId to update the avatar of
      * @param newAvatarUri the new avatar uri of the user
      * @param fileName the fileName of selected image
@@ -74,12 +74,12 @@ interface ProfileService {
     suspend fun getProfile(userId: String): JsonDict
 
     /**
-     * Get the current user 3Pids
+     * Get the current user 3Pids.
      */
     fun getThreePids(): List
 
     /**
-     * Get the current user 3Pids Live
+     * Get the current user 3Pids Live.
      * @param refreshData set to true to fetch data from the homeserver
      */
     fun getThreePidsLive(refreshData: Boolean): LiveData>
@@ -90,7 +90,7 @@ interface ProfileService {
     fun getPendingThreePids(): List
 
     /**
-     * Get the pending 3Pids Live
+     * Get the pending 3Pids Live.
      */
     fun getPendingThreePidsLive(): LiveData>
 
@@ -100,18 +100,18 @@ interface ProfileService {
     suspend fun addThreePid(threePid: ThreePid)
 
     /**
-     * Validate a code received by text message
+     * Validate a code received by text message.
      */
     suspend fun submitSmsCode(threePid: ThreePid.Msisdn, code: String)
 
     /**
-     * Finalize adding a 3Pids. Call this method once the user has validated that he owns the ThreePid
+     * Finalize adding a 3Pids. Call this method once the user has validated that he owns the ThreePid.
      */
     suspend fun finalizeAddingThreePid(threePid: ThreePid,
                                        userInteractiveAuthInterceptor: UserInteractiveAuthInterceptor)
 
     /**
-     * Cancel adding a threepid. It will remove locally stored data about this ThreePid
+     * Cancel adding a threepid. It will remove locally stored data about this ThreePid.
      */
     suspend fun cancelAddingThreePid(threePid: ThreePid)
 
@@ -121,7 +121,7 @@ interface ProfileService {
     suspend fun deleteThreePid(threePid: ThreePid)
 
     /**
-     * Return a User object from a userId
+     * Return a User object from a userId.
      */
     suspend fun getProfileAsUser(userId: String): User {
         return getProfile(userId).let { dict ->
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/pushers/PushersService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/pushers/PushersService.kt
index f884d3e890..5f9857eb2f 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/pushers/PushersService.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/pushers/PushersService.kt
@@ -21,7 +21,7 @@ import java.util.UUID
 interface PushersService {
 
     /**
-     * Refresh pushers from server state
+     * Refresh pushers from server state.
      */
     fun refreshPushers()
 
@@ -66,7 +66,7 @@ interface PushersService {
                                append: Boolean = true)
 
     /**
-     * Directly ask the push gateway to send a push to this device
+     * Directly ask the push gateway to send a push to this device.
      * If successful, the push gateway has accepted the request. In this case, the app should receive a Push with the provided eventId.
      * In case of error, PusherRejected will be thrown. In this case it means that the pushkey is not valid.
      *
@@ -81,30 +81,30 @@ interface PushersService {
                          eventId: String)
 
     /**
-     * Remove a registered pusher
+     * Remove a registered pusher.
      * @param pusher the pusher to remove, can be http or email
      */
     suspend fun removePusher(pusher: Pusher)
 
     /**
-     * Remove a Http pusher by its pushkey and appId
+     * Remove a Http pusher by its pushkey and appId.
      * @see addHttpPusher
      */
     suspend fun removeHttpPusher(pushkey: String, appId: String)
 
     /**
-     * Remove an Email pusher
+     * Remove an Email pusher.
      * @see addEmailPusher
      */
     suspend fun removeEmailPusher(email: String)
 
     /**
-     * Get the current pushers, as a LiveData
+     * Get the current pushers, as a LiveData.
      */
     fun getPushersLive(): LiveData>
 
     /**
-     * Get the current pushers
+     * Get the current pushers.
      */
     fun getPushers(): List
 
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/pushrules/Action.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/pushrules/Action.kt
similarity index 97%
rename from matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/pushrules/Action.kt
rename to matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/pushrules/Action.kt
index 30289531e7..2b2930c1ba 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/pushrules/Action.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/pushrules/Action.kt
@@ -13,9 +13,9 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package org.matrix.android.sdk.api.pushrules
+package org.matrix.android.sdk.api.session.pushrules
 
-import org.matrix.android.sdk.api.pushrules.rest.PushRule
+import org.matrix.android.sdk.api.session.pushrules.rest.PushRule
 import timber.log.Timber
 
 sealed class Action {
@@ -42,7 +42,7 @@ sealed class Action {
 }
 
 /**
- * Ref: https://matrix.org/docs/spec/client_server/latest#actions
+ * Ref: https://matrix.org/docs/spec/client_server/latest#actions.
  *
  * Convert
  * 
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/pushrules/Condition.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/pushrules/Condition.kt
similarity index 93%
rename from matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/pushrules/Condition.kt
rename to matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/pushrules/Condition.kt
index 04cccf7319..df5b056c2e 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/pushrules/Condition.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/pushrules/Condition.kt
@@ -13,7 +13,7 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package org.matrix.android.sdk.api.pushrules
+package org.matrix.android.sdk.api.session.pushrules
 
 import org.matrix.android.sdk.api.session.events.model.Event
 
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/pushrules/ConditionResolver.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/pushrules/ConditionResolver.kt
similarity index 96%
rename from matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/pushrules/ConditionResolver.kt
rename to matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/pushrules/ConditionResolver.kt
index 0a7366e5d2..f8a930f987 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/pushrules/ConditionResolver.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/pushrules/ConditionResolver.kt
@@ -13,7 +13,7 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package org.matrix.android.sdk.api.pushrules
+package org.matrix.android.sdk.api.session.pushrules
 
 import org.matrix.android.sdk.api.session.events.model.Event
 
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/pushrules/ContainsDisplayNameCondition.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/pushrules/ContainsDisplayNameCondition.kt
similarity index 97%
rename from matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/pushrules/ContainsDisplayNameCondition.kt
rename to matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/pushrules/ContainsDisplayNameCondition.kt
index 7f43023873..69dd14ddc2 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/pushrules/ContainsDisplayNameCondition.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/pushrules/ContainsDisplayNameCondition.kt
@@ -13,7 +13,7 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package org.matrix.android.sdk.api.pushrules
+package org.matrix.android.sdk.api.session.pushrules
 
 import org.matrix.android.sdk.api.session.events.model.Event
 import org.matrix.android.sdk.api.session.events.model.EventType
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/pushrules/EventMatchCondition.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/pushrules/EventMatchCondition.kt
similarity index 98%
rename from matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/pushrules/EventMatchCondition.kt
rename to matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/pushrules/EventMatchCondition.kt
index 65a13b4fec..8875807b8a 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/pushrules/EventMatchCondition.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/pushrules/EventMatchCondition.kt
@@ -13,7 +13,7 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package org.matrix.android.sdk.api.pushrules
+package org.matrix.android.sdk.api.session.pushrules
 
 import org.matrix.android.sdk.api.session.events.model.Event
 import org.matrix.android.sdk.internal.di.MoshiProvider
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/pushrules/Kind.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/pushrules/Kind.kt
similarity index 96%
rename from matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/pushrules/Kind.kt
rename to matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/pushrules/Kind.kt
index 293a06af9f..463f3c2a73 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/pushrules/Kind.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/pushrules/Kind.kt
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package org.matrix.android.sdk.api.pushrules
+package org.matrix.android.sdk.api.session.pushrules
 
 enum class Kind(val value: String) {
     EventMatch("event_match"),
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/pushrules/PushEvents.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/pushrules/PushEvents.kt
similarity index 88%
rename from matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/pushrules/PushEvents.kt
rename to matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/pushrules/PushEvents.kt
index 466e345cad..ee460d7076 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/pushrules/PushEvents.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/pushrules/PushEvents.kt
@@ -13,10 +13,10 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package org.matrix.android.sdk.api.pushrules
+package org.matrix.android.sdk.api.session.pushrules
 
-import org.matrix.android.sdk.api.pushrules.rest.PushRule
 import org.matrix.android.sdk.api.session.events.model.Event
+import org.matrix.android.sdk.api.session.pushrules.rest.PushRule
 
 data class PushEvents(
         val matchedEvents: List>,
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/pushrules/PushRuleService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/pushrules/PushRuleService.kt
similarity index 89%
rename from matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/pushrules/PushRuleService.kt
rename to matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/pushrules/PushRuleService.kt
index 76885d8545..bc4860be11 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/pushrules/PushRuleService.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/pushrules/PushRuleService.kt
@@ -13,16 +13,16 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package org.matrix.android.sdk.api.pushrules
+package org.matrix.android.sdk.api.session.pushrules
 
 import androidx.lifecycle.LiveData
-import org.matrix.android.sdk.api.pushrules.rest.PushRule
-import org.matrix.android.sdk.api.pushrules.rest.RuleSet
 import org.matrix.android.sdk.api.session.events.model.Event
+import org.matrix.android.sdk.api.session.pushrules.rest.PushRule
+import org.matrix.android.sdk.api.session.pushrules.rest.RuleSet
 
 interface PushRuleService {
     /**
-     * Fetch the push rules from the server
+     * Fetch the push rules from the server.
      */
     fun fetchPushRules(scope: String = RuleScope.GLOBAL)
 
@@ -33,7 +33,7 @@ interface PushRuleService {
     suspend fun addPushRule(kind: RuleKind, pushRule: PushRule)
 
     /**
-     * Enables/Disables a push rule and updates the actions if necessary
+     * Enables/Disables a push rule and updates the actions if necessary.
      * @param enable Enables/Disables the rule
      * @param actions Actions to update if not null
      */
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/pushrules/RoomMemberCountCondition.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/pushrules/RoomMemberCountCondition.kt
similarity index 94%
rename from matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/pushrules/RoomMemberCountCondition.kt
rename to matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/pushrules/RoomMemberCountCondition.kt
index 328e6dae11..6973ff1372 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/pushrules/RoomMemberCountCondition.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/pushrules/RoomMemberCountCondition.kt
@@ -13,7 +13,7 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package org.matrix.android.sdk.api.pushrules
+package org.matrix.android.sdk.api.session.pushrules
 
 import org.matrix.android.sdk.api.session.events.model.Event
 import org.matrix.android.sdk.internal.session.room.RoomGetter
@@ -44,7 +44,7 @@ class RoomMemberCountCondition(
         // Parse the is field into prefix and number the first time
         val (prefix, count) = parseIsField() ?: return false
 
-        val numMembers = room.getNumberOfJoinedMembers()
+        val numMembers = room.membershipService().getNumberOfJoinedMembers()
 
         return when (prefix) {
             "<"  -> numMembers < count
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/pushrules/RuleIds.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/pushrules/RuleIds.kt
similarity index 97%
rename from matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/pushrules/RuleIds.kt
rename to matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/pushrules/RuleIds.kt
index 5b14e97d5e..4f35fb79c3 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/pushrules/RuleIds.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/pushrules/RuleIds.kt
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package org.matrix.android.sdk.api.pushrules
+package org.matrix.android.sdk.api.session.pushrules
 
 /**
  * Known rule ids
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/pushrules/RuleScope.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/pushrules/RuleScope.kt
similarity index 92%
rename from matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/pushrules/RuleScope.kt
rename to matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/pushrules/RuleScope.kt
index 7c1edc1aca..307b9db042 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/pushrules/RuleScope.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/pushrules/RuleScope.kt
@@ -13,7 +13,7 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package org.matrix.android.sdk.api.pushrules
+package org.matrix.android.sdk.api.session.pushrules
 
 object RuleScope {
     const val GLOBAL = "global"
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/pushrules/RuleSetKey.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/pushrules/RuleSetKey.kt
similarity index 95%
rename from matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/pushrules/RuleSetKey.kt
rename to matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/pushrules/RuleSetKey.kt
index 5b6f6713f8..7b8f4c9f95 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/pushrules/RuleSetKey.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/pushrules/RuleSetKey.kt
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package org.matrix.android.sdk.api.pushrules
+package org.matrix.android.sdk.api.session.pushrules
 
 /**
  * Ref: https://matrix.org/docs/spec/client_server/latest#get-matrix-client-r0-pushrules
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/pushrules/SenderNotificationPermissionCondition.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/pushrules/SenderNotificationPermissionCondition.kt
similarity index 97%
rename from matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/pushrules/SenderNotificationPermissionCondition.kt
rename to matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/pushrules/SenderNotificationPermissionCondition.kt
index 6675fb0ff5..82f5023c2f 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/pushrules/SenderNotificationPermissionCondition.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/pushrules/SenderNotificationPermissionCondition.kt
@@ -13,7 +13,7 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package org.matrix.android.sdk.api.pushrules
+package org.matrix.android.sdk.api.session.pushrules
 
 import org.matrix.android.sdk.api.session.events.model.Event
 import org.matrix.android.sdk.api.session.room.model.PowerLevelsContent
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/pushrules/rest/PushCondition.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/pushrules/rest/PushCondition.kt
similarity index 84%
rename from matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/pushrules/rest/PushCondition.kt
rename to matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/pushrules/rest/PushCondition.kt
index b31a1e6343..1fc8329535 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/pushrules/rest/PushCondition.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/pushrules/rest/PushCondition.kt
@@ -13,17 +13,17 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package org.matrix.android.sdk.api.pushrules.rest
+package org.matrix.android.sdk.api.session.pushrules.rest
 
 import com.squareup.moshi.Json
 import com.squareup.moshi.JsonClass
-import org.matrix.android.sdk.api.pushrules.Condition
-import org.matrix.android.sdk.api.pushrules.ContainsDisplayNameCondition
-import org.matrix.android.sdk.api.pushrules.EventMatchCondition
-import org.matrix.android.sdk.api.pushrules.Kind
-import org.matrix.android.sdk.api.pushrules.RoomMemberCountCondition
-import org.matrix.android.sdk.api.pushrules.RuleIds
-import org.matrix.android.sdk.api.pushrules.SenderNotificationPermissionCondition
+import org.matrix.android.sdk.api.session.pushrules.Condition
+import org.matrix.android.sdk.api.session.pushrules.ContainsDisplayNameCondition
+import org.matrix.android.sdk.api.session.pushrules.EventMatchCondition
+import org.matrix.android.sdk.api.session.pushrules.Kind
+import org.matrix.android.sdk.api.session.pushrules.RoomMemberCountCondition
+import org.matrix.android.sdk.api.session.pushrules.RuleIds
+import org.matrix.android.sdk.api.session.pushrules.SenderNotificationPermissionCondition
 import timber.log.Timber
 
 /**
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/pushrules/rest/PushRule.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/pushrules/rest/PushRule.kt
similarity index 92%
rename from matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/pushrules/rest/PushRule.kt
rename to matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/pushrules/rest/PushRule.kt
index 31d7770a9f..a11ffc0a98 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/pushrules/rest/PushRule.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/pushrules/rest/PushRule.kt
@@ -14,14 +14,14 @@
  * limitations under the License.
  */
 
-package org.matrix.android.sdk.api.pushrules.rest
+package org.matrix.android.sdk.api.session.pushrules.rest
 
 import com.squareup.moshi.Json
 import com.squareup.moshi.JsonClass
 import org.matrix.android.sdk.api.extensions.orFalse
-import org.matrix.android.sdk.api.pushrules.Action
-import org.matrix.android.sdk.api.pushrules.getActions
-import org.matrix.android.sdk.api.pushrules.toJson
+import org.matrix.android.sdk.api.session.pushrules.Action
+import org.matrix.android.sdk.api.session.pushrules.getActions
+import org.matrix.android.sdk.api.session.pushrules.toJson
 
 /**
  * Ref: https://matrix.org/docs/spec/client_server/latest#get-matrix-client-r0-pushrules
@@ -49,7 +49,7 @@ data class PushRule(
         @Json(name = "rule_id")
         val ruleId: String,
         /**
-         * The conditions that must hold true for an event in order for a rule to be applied to an event
+         * The conditions that must hold true for an event in order for a rule to be applied to an event.
          */
         @Json(name = "conditions")
         val conditions: List? = null,
@@ -71,7 +71,7 @@ data class PushRule(
     }
 
     /**
-     * Set the notification sound
+     * Set the notification sound.
      *
      * @param sound notification sound
      */
@@ -82,7 +82,7 @@ data class PushRule(
     }
 
     /**
-     * Remove the notification sound
+     * Remove the notification sound.
      */
     fun removeNotificationSound(): PushRule {
         return copy(
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/pushrules/rest/RuleSet.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/pushrules/rest/RuleSet.kt
similarity index 93%
rename from matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/pushrules/rest/RuleSet.kt
rename to matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/pushrules/rest/RuleSet.kt
index 46f5148714..5bf42b8252 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/pushrules/rest/RuleSet.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/pushrules/rest/RuleSet.kt
@@ -13,12 +13,12 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package org.matrix.android.sdk.api.pushrules.rest
+package org.matrix.android.sdk.api.session.pushrules.rest
 
 import com.squareup.moshi.Json
 import com.squareup.moshi.JsonClass
-import org.matrix.android.sdk.api.pushrules.RuleIds
-import org.matrix.android.sdk.api.pushrules.RuleSetKey
+import org.matrix.android.sdk.api.session.pushrules.RuleIds
+import org.matrix.android.sdk.api.session.pushrules.RuleSetKey
 
 /**
  * Ref: https://matrix.org/docs/spec/client_server/latest#get-matrix-client-r0-pushrules
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/Room.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/Room.kt
index be65b883b3..3a18cf1497 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/Room.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/Room.kt
@@ -38,74 +38,129 @@ import org.matrix.android.sdk.api.session.room.timeline.TimelineService
 import org.matrix.android.sdk.api.session.room.typing.TypingService
 import org.matrix.android.sdk.api.session.room.uploads.UploadsService
 import org.matrix.android.sdk.api.session.room.version.RoomVersionService
-import org.matrix.android.sdk.api.session.search.SearchResult
 import org.matrix.android.sdk.api.session.space.Space
 import org.matrix.android.sdk.api.util.Optional
 
 /**
  * This interface defines methods to interact within a room.
  */
-interface Room :
-        TimelineService,
-        ThreadsService,
-        ThreadsLocalService,
-        SendService,
-        DraftService,
-        ReadService,
-        TypingService,
-        AliasService,
-        TagsService,
-        MembershipService,
-        StateService,
-        UploadsService,
-        ReportingService,
-        RoomCallService,
-        RelationService,
-        RoomCryptoService,
-        RoomPushRuleService,
-        RoomAccountDataService,
-        RoomVersionService {
+interface Room {
 
     val coroutineDispatchers: MatrixCoroutineDispatchers
 
     /**
-     * The roomId of this room
+     * The roomId of this room.
      */
     val roomId: String
 
     /**
-     * A live [RoomSummary] associated with the room
+     * A live [RoomSummary] associated with the room.
      * You can observe this summary to get dynamic data from this room.
      */
     fun getRoomSummaryLive(): LiveData>
 
     /**
-     * A current snapshot of [RoomSummary] associated with the room
+     * A current snapshot of [RoomSummary] associated with the room.
      */
     fun roomSummary(): RoomSummary?
 
-    /**
-     * Generic function to search a term in a room.
-     * Ref: https://matrix.org/docs/spec/client_server/latest#module-search
-     * @param searchTerm the term to search
-     * @param nextBatch the token that retrieved from the previous response. Should be provided to get the next batch of results
-     * @param orderByRecent if true, the most recent message events will return in the first places of the list
-     * @param limit the maximum number of events to return.
-     * @param beforeLimit how many events before the result are returned.
-     * @param afterLimit how many events after the result are returned.
-     * @param includeProfile requests that the server returns the historic profile information for the users that sent the events that were returned.
-     * @return The search result
-     */
-    suspend fun search(searchTerm: String,
-                       nextBatch: String?,
-                       orderByRecent: Boolean,
-                       limit: Int,
-                       beforeLimit: Int,
-                       afterLimit: Int,
-                       includeProfile: Boolean): SearchResult
-
     /**
      * Use this room as a Space, if the type is correct.
      */
     fun asSpace(): Space?
+
+    /**
+     * Get the TimelineService associated to this Room.
+     */
+    fun timelineService(): TimelineService
+
+    /**
+     * Get the ThreadsService associated to this Room.
+     */
+    fun threadsService(): ThreadsService
+
+    /**
+     * Get the ThreadsLocalService associated to this Room.
+     */
+    fun threadsLocalService(): ThreadsLocalService
+
+    /**
+     * Get the SendService associated to this Room.
+     */
+    fun sendService(): SendService
+
+    /**
+     * Get the DraftService associated to this Room.
+     */
+    fun draftService(): DraftService
+
+    /**
+     * Get the ReadService associated to this Room.
+     */
+    fun readService(): ReadService
+
+    /**
+     * Get the TypingService associated to this Room.
+     */
+    fun typingService(): TypingService
+
+    /**
+     * Get the AliasService associated to this Room.
+     */
+    fun aliasService(): AliasService
+
+    /**
+     * Get the TagsService associated to this Room.
+     */
+    fun tagsService(): TagsService
+
+    /**
+     * Get the MembershipService associated to this Room.
+     */
+    fun membershipService(): MembershipService
+
+    /**
+     * Get the StateService associated to this Room.
+     */
+    fun stateService(): StateService
+
+    /**
+     * Get the UploadsService associated to this Room.
+     */
+    fun uploadsService(): UploadsService
+
+    /**
+     * Get the ReportingService associated to this Room.
+     */
+    fun reportingService(): ReportingService
+
+    /**
+     * Get the RoomCallService associated to this Room.
+     */
+    fun roomCallService(): RoomCallService
+
+    /**
+     * Get the RelationService associated to this Room.
+     */
+    fun relationService(): RelationService
+
+    /**
+     * Get the RoomCryptoService associated to this Room.
+     */
+    fun roomCryptoService(): RoomCryptoService
+
+    /**
+     * Get the RoomPushRuleService associated to this Room.
+     */
+    fun roomPushRuleService(): RoomPushRuleService
+
+    /**
+     * Get the RoomAccountDataService associated to this Room.
+     */
+    fun roomAccountDataService(): RoomAccountDataService
+
+    /**
+     * Get the RoomVersionService associated to this Room.
+     */
+    fun roomVersionService(): RoomVersionService
 }
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/RoomDirectoryService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/RoomDirectoryService.kt
index 9446f0fdff..cb70603e66 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/RoomDirectoryService.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/RoomDirectoryService.kt
@@ -26,18 +26,18 @@ import org.matrix.android.sdk.api.session.room.model.roomdirectory.PublicRoomsRe
 interface RoomDirectoryService {
 
     /**
-     * Get rooms from directory
+     * Get rooms from directory.
      */
     suspend fun getPublicRooms(server: String?,
                                publicRoomsParams: PublicRoomsParams): PublicRoomsResponse
 
     /**
-     * Get the visibility of a room in the directory
+     * Get the visibility of a room in the directory.
      */
     suspend fun getRoomDirectoryVisibility(roomId: String): RoomDirectoryVisibility
 
     /**
-     * Set the visibility of a room in the directory
+     * Set the visibility of a room in the directory.
      */
     suspend fun setRoomDirectoryVisibility(roomId: String, roomDirectoryVisibility: RoomDirectoryVisibility)
 
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/RoomExtensions.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/RoomExtensions.kt
new file mode 100644
index 0000000000..0e631427bd
--- /dev/null
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/RoomExtensions.kt
@@ -0,0 +1,33 @@
+/*
+ * Copyright (c) 2022 The Matrix.org Foundation C.I.C.
+ *
+ * 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 org.matrix.android.sdk.api.session.room
+
+import org.matrix.android.sdk.api.query.QueryStringValue
+import org.matrix.android.sdk.api.session.events.model.Event
+import org.matrix.android.sdk.api.session.room.timeline.TimelineEvent
+
+/**
+ * Get a TimelineEvent using the TimelineService of a Room.
+ */
+fun Room.getTimelineEvent(eventId: String): TimelineEvent? =
+        timelineService().getTimelineEvent(eventId)
+
+/**
+ * Get a StateEvent using the StateService of a Room.
+ */
+fun Room.getStateEvent(eventType: String, stateKey: QueryStringValue = QueryStringValue.NoCondition): Event? =
+        stateService().getStateEvent(eventType, stateKey)
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/RoomService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/RoomService.kt
index 700e292b0c..6d5551ddf0 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/RoomService.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/RoomService.kt
@@ -36,12 +36,12 @@ import org.matrix.android.sdk.api.util.Optional
 interface RoomService {
 
     /**
-     * Create a room asynchronously
+     * Create a room asynchronously.
      */
     suspend fun createRoom(createRoomParams: CreateRoomParams): String
 
     /**
-     * Create a direct room asynchronously. This is a facility method to create a direct room with the necessary parameters
+     * Create a direct room asynchronously. This is a facility method to create a direct room with the necessary parameters.
      */
     suspend fun createDirectRoom(otherUserId: String): String {
         return createRoom(
@@ -55,7 +55,7 @@ interface RoomService {
     }
 
     /**
-     * Join a room by id
+     * Join a room by id.
      * @param roomIdOrAlias the roomId or the room alias of the room to join
      * @param reason optional reason for joining the room
      * @param viaServers the servers to attempt to join the room through. One of the servers must be participating in the room.
@@ -84,14 +84,14 @@ interface RoomService {
     suspend fun leaveRoom(roomId: String, reason: String? = null)
 
     /**
-     * Get a room from a roomId
+     * Get a room from a roomId.
      * @param roomId the roomId to look for.
      * @return a room with roomId or null
      */
     fun getRoom(roomId: String): Room?
 
     /**
-     * Get a roomSummary from a roomId or a room alias
+     * Get a roomSummary from a roomId or a room alias.
      * @param roomIdOrAlias the roomId or the alias of a room to look for.
      * @return a matching room summary or null
      */
@@ -112,14 +112,14 @@ interface RoomService {
                              sortOrder: RoomSortOrder = RoomSortOrder.ACTIVITY): LiveData>
 
     /**
-     * Get a snapshot list of Breadcrumbs
+     * Get a snapshot list of Breadcrumbs.
      * @param queryParams parameters to query the room summaries. It can be use to keep only joined rooms, for instance.
      * @return the immutable list of [RoomSummary]
      */
     fun getBreadcrumbs(queryParams: RoomSummaryQueryParams): List
 
     /**
-     * Get a live list of Breadcrumbs
+     * Get a live list of Breadcrumbs.
      * @param queryParams parameters to query the room summaries. It can be use to keep only joined rooms, for instance.
      * @return the [LiveData] of [RoomSummary]
      */
@@ -132,7 +132,7 @@ interface RoomService {
     suspend fun onRoomDisplayed(roomId: String)
 
     /**
-     * Mark all rooms as read
+     * Mark all rooms as read.
      */
     suspend fun markAllAsRead(roomIds: List)
 
@@ -143,7 +143,7 @@ interface RoomService {
                                  searchOnServer: Boolean): Optional
 
     /**
-     * Delete a room alias
+     * Delete a room alias.
      */
     suspend fun deleteRoomAlias(roomAlias: String)
 
@@ -162,7 +162,7 @@ interface RoomService {
     fun getChangeMembershipsLive(): LiveData>
 
     /**
-     * Return the roomId of an existing DM with the other user, or null if such room does not exist
+     * Return the roomId of an existing DM with the other user, or null if such room does not exist.
      * A room is a DM if:
      *  - it is listed in the `m.direct` account data
      *  - the current user has joined the room
@@ -175,7 +175,7 @@ interface RoomService {
     fun getExistingDirectRoomWithUser(otherUserId: String): String?
 
     /**
-     * Get a room member for the tuple {userId,roomId}
+     * Get a room member for the tuple {userId,roomId}.
      * @param userId the userId to look for.
      * @param roomId the roomId to look for.
      * @return the room member or null
@@ -183,7 +183,7 @@ interface RoomService {
     fun getRoomMember(userId: String, roomId: String): RoomMemberSummary?
 
     /**
-     * Observe a live room member for the tuple {userId,roomId}
+     * Observe a live room member for the tuple {userId,roomId}.
      * @param userId the userId to look for.
      * @param roomId the roomId to look for.
      * @return a LiveData of the optional found room member
@@ -191,39 +191,39 @@ interface RoomService {
     fun getRoomMemberLive(userId: String, roomId: String): LiveData>
 
     /**
-     * Get some state events about a room
+     * Get some state events about a room.
      */
     suspend fun getRoomState(roomId: String): List
 
     /**
-     * Use this if you want to get information from a room that you are not yet in (or invited)
-     * It might be possible to get some information on this room if it is public or if guest access is allowed
-     * This call will try to gather some information on this room, but it could fail and get nothing more
+     * Use this if you want to get information from a room that you are not yet in (or invited).
+     * It might be possible to get some information on this room if it is public or if guest access is allowed.
+     * This call will try to gather some information on this room, but it could fail and get nothing more.
      */
     suspend fun peekRoom(roomIdOrAlias: String): PeekResult
 
     /**
-     * TODO Doc
+     * TODO Doc.
      */
     fun getPagedRoomSummariesLive(queryParams: RoomSummaryQueryParams,
                                   pagedListConfig: PagedList.Config = defaultPagedListConfig,
                                   sortOrder: RoomSortOrder = RoomSortOrder.ACTIVITY): LiveData>
 
     /**
-     * TODO Doc
+     * TODO Doc.
      */
     fun getFilteredPagedRoomSummariesLive(queryParams: RoomSummaryQueryParams,
                                           pagedListConfig: PagedList.Config = defaultPagedListConfig,
                                           sortOrder: RoomSortOrder = RoomSortOrder.ACTIVITY): UpdatableLivePageResult
 
     /**
-     * Return a LiveData on the number of rooms
+     * Return a LiveData on the number of rooms.
      * @param queryParams parameters to query the room summaries. It can be use to keep only joined rooms, for instance.
      */
     fun getRoomCountLive(queryParams: RoomSummaryQueryParams): LiveData
 
     /**
-     * TODO Doc
+     * TODO Doc.
      */
     fun getNotificationCountForRooms(queryParams: RoomSummaryQueryParams): RoomAggregateNotificationCount
 
@@ -238,16 +238,16 @@ interface RoomService {
     fun getFlattenRoomSummaryChildrenOf(spaceId: String?, memberships: List = Membership.activeMemberships()): List
 
     /**
-     * Returns all the children of this space, as LiveData
+     * Returns all the children of this space, as LiveData.
      */
     fun getFlattenRoomSummaryChildrenOfLive(spaceId: String?,
                                             memberships: List = Membership.activeMemberships()): LiveData>
 
     /**
-     * Refreshes the RoomSummary LatestPreviewContent for the given @param roomId
-     * If the roomId is null, all rooms are updated
+     * Refreshes the RoomSummary LatestPreviewContent for the given @param roomId.
+     * If the roomId is null, all rooms are updated.
      *
-     * This is useful for refreshing summary content with encrypted messages after receiving new room keys
+     * This is useful for refreshing summary content with encrypted messages after receiving new room keys.
      */
     fun refreshJoinedRoomSummaryPreviews(roomId: String?)
 }
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/RoomSummaryQueryParams.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/RoomSummaryQueryParams.kt
index b440857518..5c74dcced1 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/RoomSummaryQueryParams.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/RoomSummaryQueryParams.kt
@@ -41,7 +41,7 @@ fun spaceSummaryQueryParams(init: (RoomSummaryQueryParams.Builder.() -> Unit) =
 
 /**
  * This class can be used to filter room summaries to use with:
- * [org.matrix.android.sdk.api.session.room.Room] and [org.matrix.android.sdk.api.session.room.RoomService]
+ * [org.matrix.android.sdk.api.session.room.Room] and [org.matrix.android.sdk.api.session.room.RoomService].
  */
 data class RoomSummaryQueryParams(
         val roomId: QueryStringValue,
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/accountdata/RoomAccountDataService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/accountdata/RoomAccountDataService.kt
index 190749c85c..b6925dd4a3 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/accountdata/RoomAccountDataService.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/accountdata/RoomAccountDataService.kt
@@ -25,29 +25,29 @@ import org.matrix.android.sdk.api.util.Optional
  */
 interface RoomAccountDataService {
     /**
-     * Retrieve the account data with the provided type or null if not found
+     * Retrieve the account data with the provided type or null if not found.
      */
     fun getAccountDataEvent(type: String): RoomAccountDataEvent?
 
     /**
-     * Observe the account data with the provided type
+     * Observe the account data with the provided type.
      */
     fun getLiveAccountDataEvent(type: String): LiveData>
 
     /**
      * Retrieve the account data with the provided types. The return list can have a different size that
      * the size of the types set, because some AccountData may not exist.
-     * If an empty set is provided, all the AccountData are retrieved
+     * If an empty set is provided, all the AccountData are retrieved.
      */
     fun getAccountDataEvents(types: Set): List
 
     /**
-     * Observe the account data with the provided types. If an empty set is provided, all the AccountData are observed
+     * Observe the account data with the provided types. If an empty set is provided, all the AccountData are observed.
      */
     fun getLiveAccountDataEvents(types: Set): LiveData>
 
     /**
-     * Update the account data with the provided type and the provided account data content
+     * Update the account data with the provided type and the provided account data content.
      */
     suspend fun updateAccountData(type: String, content: Content)
 }
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/alias/AliasService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/alias/AliasService.kt
index 5fe7e99425..2073db15b3 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/alias/AliasService.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/alias/AliasService.kt
@@ -18,13 +18,13 @@ package org.matrix.android.sdk.api.session.room.alias
 
 interface AliasService {
     /**
-     * Get list of local alias of the room
+     * Get list of local alias of the room.
      * @return the list of the aliases (full aliases, not only the local part)
      */
     suspend fun getRoomAliases(): List
 
     /**
-     * Add local alias to the room
+     * Add local alias to the room.
      * @param aliasLocalPart the local part of the alias.
      * Ex: for the alias "#my_alias:example.org", the local part is "my_alias"
      */
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/call/RoomCallService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/call/RoomCallService.kt
index cac5217dd6..4439253a57 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/call/RoomCallService.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/call/RoomCallService.kt
@@ -21,7 +21,7 @@ package org.matrix.android.sdk.api.session.room.call
  */
 interface RoomCallService {
     /**
-     * Return true if calls (audio or video) can be performed on this Room
+     * Return true if calls (audio or video) can be performed on this Room.
      */
     fun canStartCall(): Boolean
 }
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/members/MembershipService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/members/MembershipService.kt
index 6c8e2d310c..e7ac69be74 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/members/MembershipService.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/members/MembershipService.kt
@@ -39,14 +39,14 @@ interface MembershipService {
     fun getRoomMember(userId: String): RoomMemberSummary?
 
     /**
-     * Return all the roomMembers of the room with params
+     * Return all the roomMembers of the room with params.
      * @param queryParams the params to query for
      * @return a roomMember list.
      */
     fun getRoomMembers(queryParams: RoomMemberQueryParams): List
 
     /**
-     * Return all the roomMembers of the room filtered by memberships
+     * Return all the roomMembers of the room filtered by memberships.
      * @param queryParams the params to query for
      * @return a [LiveData] of roomMember list.
      */
@@ -55,27 +55,27 @@ interface MembershipService {
     fun getNumberOfJoinedMembers(): Int
 
     /**
-     * Invite a user in the room
+     * Invite a user in the room.
      */
     suspend fun invite(userId: String, reason: String? = null)
 
     /**
-     * Invite a user with email or phone number in the room
+     * Invite a user with email or phone number in the room.
      */
     suspend fun invite3pid(threePid: ThreePid)
 
     /**
-     * Ban a user from the room
+     * Ban a user from the room.
      */
     suspend fun ban(userId: String, reason: String? = null)
 
     /**
-     * Unban a user from the room
+     * Unban a user from the room.
      */
     suspend fun unban(userId: String, reason: String? = null)
 
     /**
-     * Remove a user from the room
+     * Remove a user from the room.
      */
     suspend fun remove(userId: String, reason: String? = null)
 
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/members/RoomMemberQueryParams.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/members/RoomMemberQueryParams.kt
index c2c5a7f804..dd83066dbb 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/members/RoomMemberQueryParams.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/members/RoomMemberQueryParams.kt
@@ -24,7 +24,7 @@ fun roomMemberQueryParams(init: (RoomMemberQueryParams.Builder.() -> Unit) = {})
 }
 
 /**
- * This class can be used to filter room members
+ * This class can be used to filter room members.
  */
 data class RoomMemberQueryParams(
         val displayName: QueryStringValue,
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/EventAnnotationsSummary.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/EventAnnotationsSummary.kt
index 0238eb6c8d..0aded20339 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/EventAnnotationsSummary.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/EventAnnotationsSummary.kt
@@ -15,10 +15,12 @@
  */
 package org.matrix.android.sdk.api.session.room.model
 
+import org.matrix.android.sdk.api.session.room.model.livelocation.LiveLocationShareAggregatedSummary
+
 data class EventAnnotationsSummary(
-        val eventId: String,
         val reactionsSummary: List = emptyList(),
         val editSummary: EditAggregatedSummary? = null,
         val pollResponseSummary: PollResponseAggregatedSummary? = null,
-        val referencesAggregatedSummary: ReferencesAggregatedSummary? = null
+        val referencesAggregatedSummary: ReferencesAggregatedSummary? = null,
+        val liveLocationShareAggregatedSummary: LiveLocationShareAggregatedSummary? = null,
 )
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/Invite.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/Invite.kt
index 2841da35d1..f3c83c223a 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/Invite.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/Invite.kt
@@ -20,7 +20,7 @@ import com.squareup.moshi.Json
 import com.squareup.moshi.JsonClass
 
 /**
- * Subclass representing a search API response
+ * Subclass representing a search API response.
  */
 @JsonClass(generateAdapter = true)
 data class Invite(
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/Membership.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/Membership.kt
index a5d0f63722..c0325d87ec 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/Membership.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/Membership.kt
@@ -20,7 +20,7 @@ import com.squareup.moshi.Json
 import com.squareup.moshi.JsonClass
 
 /**
- * Represents the membership of a user on a room
+ * Represents the membership of a user on a room.
  */
 @JsonClass(generateAdapter = false)
 enum class Membership {
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/PowerLevelsContent.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/PowerLevelsContent.kt
index 5c46db7166..8ef94b2896 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/PowerLevelsContent.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/PowerLevelsContent.kt
@@ -67,7 +67,7 @@ data class PowerLevelsContent(
         @Json(name = "notifications") val notifications: Map? = null
 ) {
     /**
-     * Return a copy of this content with a new power level for the specified user
+     * Return a copy of this content with a new power level for the specified user.
      *
      * @param userId the userId to alter the power level of
      * @param powerLevel the new power level, or null to set the default value.
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/ReferencesAggregatedSummary.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/ReferencesAggregatedSummary.kt
index 31ac09efb8..0bc87c9bf1 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/ReferencesAggregatedSummary.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/ReferencesAggregatedSummary.kt
@@ -19,10 +19,9 @@ import org.matrix.android.sdk.api.session.events.model.Content
 
 /**
  * Events can relates to other events, this object keeps a summary
- * of all events that are referencing the 'eventId' event via the RelationType.REFERENCE
+ * of all events that are referencing the 'eventId' event via the RelationType.REFERENCE.
  */
 data class ReferencesAggregatedSummary(
-        val eventId: String,
         val content: Content?,
         val sourceEvents: List,
         val localEchos: List
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/RoomAvatarContent.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/RoomAvatarContent.kt
index 8c1c9e6b12..b6567fcf21 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/RoomAvatarContent.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/RoomAvatarContent.kt
@@ -20,7 +20,7 @@ import com.squareup.moshi.Json
 import com.squareup.moshi.JsonClass
 
 /**
- * Class representing the EventType.STATE_ROOM_AVATAR state event content
+ * Class representing the EventType.STATE_ROOM_AVATAR state event content.
  */
 @JsonClass(generateAdapter = true)
 data class RoomAvatarContent(
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/RoomCanonicalAliasContent.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/RoomCanonicalAliasContent.kt
index 4e8bd2e71b..1e76bef6f3 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/RoomCanonicalAliasContent.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/RoomCanonicalAliasContent.kt
@@ -20,7 +20,7 @@ import com.squareup.moshi.Json
 import com.squareup.moshi.JsonClass
 
 /**
- * Class representing the EventType.STATE_ROOM_CANONICAL_ALIAS state event content
+ * Class representing the EventType.STATE_ROOM_CANONICAL_ALIAS state event content.
  */
 @JsonClass(generateAdapter = true)
 data class RoomCanonicalAliasContent(
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/RoomGuestAccessContent.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/RoomGuestAccessContent.kt
index 020e7ed39e..ba274325bc 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/RoomGuestAccessContent.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/RoomGuestAccessContent.kt
@@ -27,13 +27,13 @@ import timber.log.Timber
 @JsonClass(generateAdapter = true)
 data class RoomGuestAccessContent(
         // Required. Whether guests can join the room. One of: ["can_join", "forbidden"]
-        @Json(name = "guest_access") val _guestAccess: String? = null
+        @Json(name = "guest_access") val guestAccessStr: String? = null
 ) {
-    val guestAccess: GuestAccess? = when (_guestAccess) {
+    val guestAccess: GuestAccess? = when (guestAccessStr) {
         "can_join"  -> GuestAccess.CanJoin
         "forbidden" -> GuestAccess.Forbidden
         else        -> {
-            Timber.w("Invalid value for GuestAccess: `$_guestAccess`")
+            Timber.w("Invalid value for GuestAccess: `$guestAccessStr`")
             null
         }
     }
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/RoomHistoryVisibilityContent.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/RoomHistoryVisibilityContent.kt
index 3ac14e48de..da5c90ff05 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/RoomHistoryVisibilityContent.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/RoomHistoryVisibilityContent.kt
@@ -22,15 +22,15 @@ import timber.log.Timber
 
 @JsonClass(generateAdapter = true)
 data class RoomHistoryVisibilityContent(
-        @Json(name = "history_visibility") val _historyVisibility: String? = null
+        @Json(name = "history_visibility") val historyVisibilityStr: String? = null
 ) {
-    val historyVisibility: RoomHistoryVisibility? = when (_historyVisibility) {
+    val historyVisibility: RoomHistoryVisibility? = when (historyVisibilityStr) {
         "world_readable" -> RoomHistoryVisibility.WORLD_READABLE
         "shared"         -> RoomHistoryVisibility.SHARED
         "invited"        -> RoomHistoryVisibility.INVITED
         "joined"         -> RoomHistoryVisibility.JOINED
         else             -> {
-            Timber.w("Invalid value for RoomHistoryVisibility: `$_historyVisibility`")
+            Timber.w("Invalid value for RoomHistoryVisibility: `$historyVisibilityStr`")
             null
         }
     }
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/RoomJoinRulesContent.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/RoomJoinRulesContent.kt
index 871b299f93..3b338a36cd 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/RoomJoinRulesContent.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/RoomJoinRulesContent.kt
@@ -23,11 +23,11 @@ import com.squareup.moshi.JsonClass
 import timber.log.Timber
 
 /**
- * Class representing the EventType.STATE_ROOM_JOIN_RULES state event content
+ * Class representing the EventType.STATE_ROOM_JOIN_RULES state event content.
  */
 @JsonClass(generateAdapter = true)
 data class RoomJoinRulesContent(
-        @Json(name = "join_rule") val _joinRules: String? = null,
+        @Json(name = "join_rule") val joinRulesStr: String? = null,
         /**
          * If the allow key is an empty list (or not a list at all),
          * then no users are allowed to join without an invite.
@@ -35,14 +35,14 @@ data class RoomJoinRulesContent(
          */
         @Json(name = "allow") val allowList: List? = null
 ) {
-    val joinRules: RoomJoinRules? = when (_joinRules) {
-        "public" -> RoomJoinRules.PUBLIC
-        "invite" -> RoomJoinRules.INVITE
-        "knock" -> RoomJoinRules.KNOCK
-        "private" -> RoomJoinRules.PRIVATE
+    val joinRules: RoomJoinRules? = when (joinRulesStr) {
+        "public"     -> RoomJoinRules.PUBLIC
+        "invite"     -> RoomJoinRules.INVITE
+        "knock"      -> RoomJoinRules.KNOCK
+        "private"    -> RoomJoinRules.PRIVATE
         "restricted" -> RoomJoinRules.RESTRICTED
         else         -> {
-            Timber.w("Invalid value for RoomJoinRules: `$_joinRules`")
+            Timber.w("Invalid value for RoomJoinRules: `$joinRulesStr`")
             null
         }
     }
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/RoomMemberContent.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/RoomMemberContent.kt
index f29dd6a3e5..2529edbfdd 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/RoomMemberContent.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/RoomMemberContent.kt
@@ -21,7 +21,7 @@ import com.squareup.moshi.JsonClass
 import org.matrix.android.sdk.api.session.events.model.UnsignedData
 
 /**
- * Class representing the EventType.STATE_ROOM_MEMBER state event content
+ * Class representing the EventType.STATE_ROOM_MEMBER state event content.
  */
 @JsonClass(generateAdapter = true)
 data class RoomMemberContent(
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/RoomMemberSummary.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/RoomMemberSummary.kt
index 39177a4296..8e7382190a 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/RoomMemberSummary.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/RoomMemberSummary.kt
@@ -19,7 +19,7 @@ package org.matrix.android.sdk.api.session.room.model
 import org.matrix.android.sdk.api.session.presence.model.UserPresence
 
 /**
- * Class representing a simplified version of EventType.STATE_ROOM_MEMBER state event content
+ * Class representing a simplified version of EventType.STATE_ROOM_MEMBER state event content.
  */
 data class RoomMemberSummary constructor(
         val membership: Membership,
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/RoomNameContent.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/RoomNameContent.kt
index a0b45e881b..2dbb5f9e57 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/RoomNameContent.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/RoomNameContent.kt
@@ -20,7 +20,7 @@ import com.squareup.moshi.Json
 import com.squareup.moshi.JsonClass
 
 /**
- * Class representing the EventType.STATE_ROOM_NAME state event content
+ * Class representing the EventType.STATE_ROOM_NAME state event content.
  */
 @JsonClass(generateAdapter = true)
 data class RoomNameContent(
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/RoomStrippedState.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/RoomStrippedState.kt
index dc0c00b282..b4f663f801 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/RoomStrippedState.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/RoomStrippedState.kt
@@ -79,7 +79,7 @@ data class RoomStrippedState(
         val avatarUrl: String? = null,
 
         /**
-         * Undocumented item
+         * Undocumented item.
          */
         @Json(name = "m.federate")
         val isFederated: Boolean = false,
@@ -103,7 +103,7 @@ data class RoomStrippedState(
         val membership: String?
 ) {
     /**
-     * Return the canonical alias, or the first alias from the list of aliases, or null
+     * Return the canonical alias, or the first alias from the list of aliases, or null.
      */
     fun getPrimaryAlias(): String? {
         return canonicalAlias ?: aliases?.firstOrNull()
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/RoomTopicContent.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/RoomTopicContent.kt
index b97ee44dee..18092f12ac 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/RoomTopicContent.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/RoomTopicContent.kt
@@ -20,7 +20,7 @@ import com.squareup.moshi.Json
 import com.squareup.moshi.JsonClass
 
 /**
- * Class representing the EventType.STATE_ROOM_TOPIC state event content
+ * Class representing the EventType.STATE_ROOM_TOPIC state event content.
  */
 @JsonClass(generateAdapter = true)
 data class RoomTopicContent(
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/call/CallAnswerContent.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/call/CallAnswerContent.kt
index 6b4d782832..67ef85787e 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/call/CallAnswerContent.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/call/CallAnswerContent.kt
@@ -44,7 +44,7 @@ data class CallAnswerContent(
          * Capability advertisement.
          */
         @Json(name = "capabilities") val capabilities: CallCapabilities? = null
-) : CallSignalingContent  {
+) : CallSignalingContent {
 
     @JsonClass(generateAdapter = true)
     data class Answer(
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/call/CallCapabilities.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/call/CallCapabilities.kt
index d911ca3b88..6937b2c2e4 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/call/CallCapabilities.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/call/CallCapabilities.kt
@@ -24,7 +24,7 @@ import org.matrix.android.sdk.api.extensions.orFalse
 data class CallCapabilities(
         /**
          * If set to true, states that the sender of the event supports the m.call.replaces event and therefore supports
-         * being transferred to another destination
+         * being transferred to another destination.
          */
         @Json(name = "m.call.transferee") val transferee: Boolean? = null
 )
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/call/CallInviteContent.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/call/CallInviteContent.kt
index d70e63d122..40038ab8ec 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/call/CallInviteContent.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/call/CallInviteContent.kt
@@ -47,7 +47,7 @@ data class CallInviteContent(
          */
         @Json(name = "lifetime") val lifetime: Int?,
         /**
-         * The field should be added for all invites where the target is a specific user
+         * The field should be added for all invites where the target is a specific user.
          */
         @Json(name = "invitee") val invitee: String? = null,
         /**
@@ -55,7 +55,7 @@ data class CallInviteContent(
          */
         @Json(name = "capabilities") val capabilities: CallCapabilities? = null
 
-) : CallSignalingContent  {
+) : CallSignalingContent {
     @JsonClass(generateAdapter = true)
     data class Offer(
             /**
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/call/CallNegotiateContent.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/call/CallNegotiateContent.kt
index bbbfbe68ab..5c6c6cda01 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/call/CallNegotiateContent.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/call/CallNegotiateContent.kt
@@ -47,7 +47,7 @@ data class CallNegotiateContent(
          */
         @Json(name = "version") override val version: String?
 
-        ) : CallSignalingContent  {
+) : CallSignalingContent {
     @JsonClass(generateAdapter = true)
     data class Description(
             /**
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/call/CallReplacesContent.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/call/CallReplacesContent.kt
index 7947b7d0bd..849fa50537 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/call/CallReplacesContent.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/call/CallReplacesContent.kt
@@ -44,7 +44,7 @@ data class CallReplacesContent(
          */
         @Json(name = "target_room") val targetRoomId: String? = null,
         /**
-         * An object giving information about the transfer target
+         * An object giving information about the transfer target.
          */
         @Json(name = "target_user") val targetUser: TargetUser? = null,
         /**
@@ -61,7 +61,7 @@ data class CallReplacesContent(
          * Required. The version of the VoIP specification this messages adheres to.
          */
         @Json(name = "version") override val version: String?
-) : CallSignalingContent  {
+) : CallSignalingContent {
 
     @JsonClass(generateAdapter = true)
     data class TargetUser(
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/create/CreateRoomParams.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/create/CreateRoomParams.kt
index ce1e0e0d14..b7b0cc890b 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/create/CreateRoomParams.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/create/CreateRoomParams.kt
@@ -69,13 +69,13 @@ open class CreateRoomParams {
     val invite3pids = mutableListOf()
 
     /**
-     * Initial Guest Access
+     * Initial Guest Access.
      */
     var guestAccess: GuestAccess? = null
 
     /**
      * If set to true, when the room will be created, if cross-signing is enabled and we can get keys for every invited users,
-     * the encryption will be enabled on the created room
+     * the encryption will be enabled on the created room.
      */
     var enableEncryptionIfInvitedUsersSupportIt: Boolean = false
 
@@ -135,7 +135,7 @@ open class CreateRoomParams {
         }
 
     /**
-     * The power level content to override in the default power level event
+     * The power level content to override in the default power level event.
      */
     var powerLevelContentOverride: PowerLevelsContent? = null
 
@@ -148,7 +148,7 @@ open class CreateRoomParams {
     }
 
     /**
-     * Supported value: MXCRYPTO_ALGORITHM_MEGOLM
+     * Supported value: MXCRYPTO_ALGORITHM_MEGOLM.
      */
     var algorithm: String? = null
         private set
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/create/Predecessor.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/create/Predecessor.kt
index f48beb299a..99c829b0e2 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/create/Predecessor.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/create/Predecessor.kt
@@ -19,7 +19,7 @@ import com.squareup.moshi.Json
 import com.squareup.moshi.JsonClass
 
 /**
- * A link to an old room in case of room versioning
+ * A link to an old room in case of room versioning.
  */
 @JsonClass(generateAdapter = true)
 data class Predecessor(
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/create/RoomCreateContent.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/create/RoomCreateContent.kt
index 52e5c0e9c7..d73c941a86 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/create/RoomCreateContent.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/create/RoomCreateContent.kt
@@ -20,7 +20,7 @@ import com.squareup.moshi.Json
 import com.squareup.moshi.JsonClass
 
 /**
- * Content of a m.room.create type event
+ * Content of a m.room.create type event.
  */
 @JsonClass(generateAdapter = true)
 data class RoomCreateContent(
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/create/RoomFeaturePreset.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/create/RoomFeaturePreset.kt
index f5f722d783..6487ad947f 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/create/RoomFeaturePreset.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/create/RoomFeaturePreset.kt
@@ -47,7 +47,7 @@ class RestrictedRoomPreset(val homeServerCapabilities: HomeServerCapabilities, v
                         type = EventType.STATE_ROOM_JOIN_RULES,
                         stateKey = "",
                         content = RoomJoinRulesContent(
-                                _joinRules = RoomJoinRules.RESTRICTED.value,
+                                joinRulesStr = RoomJoinRules.RESTRICTED.value,
                                 allowList = restrictedList
                         ).toContent()
                 )
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/livelocation/LiveLocationShareAggregatedSummary.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/livelocation/LiveLocationShareAggregatedSummary.kt
new file mode 100644
index 0000000000..0b28d62f56
--- /dev/null
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/livelocation/LiveLocationShareAggregatedSummary.kt
@@ -0,0 +1,28 @@
+/*
+ * Copyright (c) 2022 The Matrix.org Foundation C.I.C.
+ *
+ * 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 org.matrix.android.sdk.api.session.room.model.livelocation
+
+import org.matrix.android.sdk.api.session.room.model.message.MessageBeaconLocationDataContent
+
+/**
+ * Aggregation info concerning a live location share.
+ */
+data class LiveLocationShareAggregatedSummary(
+        val isActive: Boolean?,
+        val endOfLiveTimestampMillis: Long?,
+        val lastLocationDataContent: MessageBeaconLocationDataContent?,
+)
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/FileInfo.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/FileInfo.kt
index fa18bfd21f..ae786ff706 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/FileInfo.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/FileInfo.kt
@@ -49,8 +49,8 @@ data class FileInfo(
 )
 
 /**
- * Get the url of the encrypted thumbnail or of the thumbnail
+ * Get the url of the encrypted thumbnail or of the thumbnail.
  */
 fun FileInfo.getThumbnailUrl(): String? {
-        return thumbnailFile?.url ?: thumbnailUrl
+    return thumbnailFile?.url ?: thumbnailUrl
 }
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/ImageInfo.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/ImageInfo.kt
index 0099208320..ec6669d4ec 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/ImageInfo.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/ImageInfo.kt
@@ -59,8 +59,8 @@ data class ImageInfo(
 )
 
 /**
- * Get the url of the encrypted thumbnail or of the thumbnail
+ * Get the url of the encrypted thumbnail or of the thumbnail.
  */
 fun ImageInfo.getThumbnailUrl(): String? {
-        return thumbnailFile?.url ?: thumbnailUrl
+    return thumbnailFile?.url ?: thumbnailUrl
 }
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/livelocation/LiveLocationBeaconContent.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/MessageBeaconInfoContent.kt
similarity index 55%
rename from matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/livelocation/LiveLocationBeaconContent.kt
rename to matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/MessageBeaconInfoContent.kt
index 106e76eafd..f8b627e497 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/livelocation/LiveLocationBeaconContent.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/MessageBeaconInfoContent.kt
@@ -14,60 +14,59 @@
  * limitations under the License.
  */
 
-package org.matrix.android.sdk.api.session.room.model.livelocation
+package org.matrix.android.sdk.api.session.room.model.message
 
 import com.squareup.moshi.Json
 import com.squareup.moshi.JsonClass
 import org.matrix.android.sdk.api.session.events.model.Content
-import org.matrix.android.sdk.api.session.room.model.message.LocationAsset
-import org.matrix.android.sdk.api.session.room.model.message.LocationAssetType
-import org.matrix.android.sdk.api.session.room.model.message.MessageContent
-import org.matrix.android.sdk.api.session.room.model.message.MessageLiveLocationContent
-import org.matrix.android.sdk.api.session.room.model.message.MessageType
 import org.matrix.android.sdk.api.session.room.model.relation.RelationDefaultContent
 
+/**
+ * Content of the state event of type
+ * [EventType.STATE_ROOM_BEACON_INFO][org.matrix.android.sdk.api.session.events.model.EventType.STATE_ROOM_BEACON_INFO]
+ *
+ * It contains general info related to a live location share.
+ * Locations are sent in a different message related to the state event.
+ * See [MessageBeaconLocationDataContent][org.matrix.android.sdk.api.session.room.model.message.MessageBeaconLocationDataContent]
+ */
 @JsonClass(generateAdapter = true)
-data class LiveLocationBeaconContent(
+data class MessageBeaconInfoContent(
         /**
-         * Local message type, not from server
+         * Local message type, not from server.
          */
         @Transient
-        override val msgType: String = MessageType.MSGTYPE_LIVE_LOCATION_STATE,
+        override val msgType: String = MessageType.MSGTYPE_BEACON_INFO,
 
         @Json(name = "body") override val body: String = "",
         @Json(name = "m.relates_to") override val relatesTo: RelationDefaultContent? = null,
         @Json(name = "m.new_content") override val newContent: Content? = null,
 
         /**
-         * Indicates user's intent to share ephemeral location.
+         * Optional description of the beacon.
          */
-        @Json(name = "org.matrix.msc3672.beacon_info") val unstableBeaconInfo: BeaconInfo? = null,
-        @Json(name = "m.beacon_info") val beaconInfo: BeaconInfo? = null,
+        @Json(name = "description") val description: String? = null,
+        /**
+         * Beacon should be considered as inactive after this timeout as milliseconds.
+         */
+        @Json(name = "timeout") val timeout: Long? = null,
+        /**
+         * Should be set true to start sharing beacon.
+         */
+        @Json(name = "live") val isLive: Boolean? = null,
+
         /**
          * Beacon creation timestamp.
          */
-        @Json(name = "org.matrix.msc3488.ts") val unstableTimestampAsMilliseconds: Long? = null,
-        @Json(name = "m.ts") val timestampAsMilliseconds: Long? = null,
+        @Json(name = "org.matrix.msc3488.ts") val unstableTimestampMillis: Long? = null,
+        @Json(name = "m.ts") val timestampMillis: Long? = null,
         /**
          * Live location asset type.
          */
         @Json(name = "org.matrix.msc3488.asset") val unstableLocationAsset: LocationAsset = LocationAsset(LocationAssetType.SELF),
         @Json(name = "m.asset") val locationAsset: LocationAsset? = null,
-
-        /**
-         * Client side tracking of the last location
-         */
-        var lastLocationContent: MessageLiveLocationContent? = null,
-
-        /**
-         * Client side tracking of whether the beacon has timed out.
-         */
-        var hasTimedOut: Boolean = false
 ) : MessageContent {
 
-    fun getBestBeaconInfo() = beaconInfo ?: unstableBeaconInfo
-
-    fun getBestTimestampAsMilliseconds() = timestampAsMilliseconds ?: unstableTimestampAsMilliseconds
+    fun getBestTimestampMillis() = timestampMillis ?: unstableTimestampMillis
 
     fun getBestLocationAsset() = locationAsset ?: unstableLocationAsset
 }
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/MessageLiveLocationContent.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/MessageBeaconLocationDataContent.kt
similarity index 67%
rename from matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/MessageLiveLocationContent.kt
rename to matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/MessageBeaconLocationDataContent.kt
index 548dc85369..e261ab5206 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/MessageLiveLocationContent.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/MessageBeaconLocationDataContent.kt
@@ -21,32 +21,40 @@ import com.squareup.moshi.JsonClass
 import org.matrix.android.sdk.api.session.events.model.Content
 import org.matrix.android.sdk.api.session.room.model.relation.RelationDefaultContent
 
+/**
+ * Content of the state event of type
+ * [EventType.BEACON_LOCATION_DATA][org.matrix.android.sdk.api.session.events.model.EventType.BEACON_LOCATION_DATA]
+ *
+ * It contains location data related to a live location share.
+ * It is related to the state event that originally started the live.
+ * See [MessageBeaconInfoContent][org.matrix.android.sdk.api.session.room.model.message.MessageBeaconInfoContent]
+ */
 @JsonClass(generateAdapter = true)
-data class MessageLiveLocationContent(
+data class MessageBeaconLocationDataContent(
         /**
-         * Local message type, not from server
+         * Local message type, not from server.
          */
         @Transient
-        override val msgType: String = MessageType.MSGTYPE_LIVE_LOCATION,
+        override val msgType: String = MessageType.MSGTYPE_BEACON_LOCATION_DATA,
 
         @Json(name = "body") override val body: String = "",
         @Json(name = "m.relates_to") override val relatesTo: RelationDefaultContent? = null,
         @Json(name = "m.new_content") override val newContent: Content? = null,
 
         /**
-         * See [MSC3488](https://github.com/matrix-org/matrix-doc/blob/matthew/location/proposals/3488-location.md)
+         * See [MSC3488](https://github.com/matrix-org/matrix-doc/blob/matthew/location/proposals/3488-location.md).
          */
         @Json(name = "org.matrix.msc3488.location") val unstableLocationInfo: LocationInfo? = null,
         @Json(name = "m.location") val locationInfo: LocationInfo? = null,
 
         /**
-         * Exact time that the data in the event refers to (milliseconds since the UNIX epoch)
+         * Exact time that the data in the event refers to (milliseconds since the UNIX epoch).
          */
-        @Json(name = "org.matrix.msc3488.ts") val unstableTimestampAsMilliseconds: Long? = null,
-        @Json(name = "m.ts") val timestampAsMilliseconds: Long? = null
+        @Json(name = "org.matrix.msc3488.ts") val unstableTimestampMillis: Long? = null,
+        @Json(name = "m.ts") val timestampMillis: Long? = null
 ) : MessageContent {
 
     fun getBestLocationInfo() = locationInfo ?: unstableLocationInfo
 
-    fun getBestTimestampAsMilliseconds() = timestampAsMilliseconds ?: unstableTimestampAsMilliseconds
+    fun getBestTimestampMillis() = timestampMillis ?: unstableTimestampMillis
 }
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/MessageContentWithFormattedBody.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/MessageContentWithFormattedBody.kt
index aabf6173d7..58ea8db02d 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/MessageContentWithFormattedBody.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/MessageContentWithFormattedBody.kt
@@ -28,7 +28,7 @@ interface MessageContentWithFormattedBody : MessageContent {
     val formattedBody: String?
 
     /**
-     * Get the formattedBody, only if not blank and if the format is equal to "org.matrix.custom.html"
+     * Get the formattedBody, only if not blank and if the format is equal to "org.matrix.custom.html".
      */
     val matrixFormattedBody: String?
         get() = formattedBody?.takeIf { it.isNotBlank() && format == MessageFormat.FORMAT_MATRIX_HTML }
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/MessageEndPollContent.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/MessageEndPollContent.kt
index 491b71477e..f0511903d0 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/MessageEndPollContent.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/MessageEndPollContent.kt
@@ -21,7 +21,7 @@ import com.squareup.moshi.JsonClass
 import org.matrix.android.sdk.api.session.room.model.relation.RelationDefaultContent
 
 /**
- * Class representing the org.matrix.msc3381.poll.end event content
+ * Class representing the org.matrix.msc3381.poll.end event content.
  */
 @JsonClass(generateAdapter = true)
 data class MessageEndPollContent(
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/MessageImageInfoContent.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/MessageImageInfoContent.kt
index 369a1a1a46..e2b69eff33 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/MessageImageInfoContent.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/MessageImageInfoContent.kt
@@ -18,7 +18,7 @@
 package org.matrix.android.sdk.api.session.room.model.message
 
 /**
- * A content with image information
+ * A content with image information.
  */
 interface MessageImageInfoContent : MessageWithAttachmentContent {
     val info: ImageInfo?
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/MessageLocationContent.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/MessageLocationContent.kt
index 2052133b06..0a66a6e400 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/MessageLocationContent.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/MessageLocationContent.kt
@@ -42,15 +42,15 @@ data class MessageLocationContent(
         @Json(name = "m.relates_to") override val relatesTo: RelationDefaultContent? = null,
         @Json(name = "m.new_content") override val newContent: Content? = null,
         /**
-         * See [MSC3488](https://github.com/matrix-org/matrix-doc/blob/matthew/location/proposals/3488-location.md)
+         * See [MSC3488](https://github.com/matrix-org/matrix-doc/blob/matthew/location/proposals/3488-location.md).
          */
         @Json(name = "org.matrix.msc3488.location") val unstableLocationInfo: LocationInfo? = null,
         @Json(name = "m.location") val locationInfo: LocationInfo? = null,
         /**
-         * Exact time that the data in the event refers to (milliseconds since the UNIX epoch)
+         * Exact time that the data in the event refers to (milliseconds since the UNIX epoch).
          */
-        @Json(name = "org.matrix.msc3488.ts") val unstableTs: Long? = null,
-        @Json(name = "m.ts") val ts: Long? = null,
+        @Json(name = "org.matrix.msc3488.ts") val unstableTimestampMillis: Long? = null,
+        @Json(name = "m.ts") val timestampMillis: Long? = null,
         @Json(name = "org.matrix.msc1767.text") val unstableText: String? = null,
         @Json(name = "m.text") val text: String? = null,
         /**
@@ -66,7 +66,7 @@ data class MessageLocationContent(
 
     fun getBestLocationInfo() = locationInfo ?: unstableLocationInfo
 
-    fun getBestTs() = ts ?: unstableTs
+    fun getBestTimestampMillis() = timestampMillis ?: unstableTimestampMillis
 
     fun getBestText() = text ?: unstableText
 
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/MessagePollContent.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/MessagePollContent.kt
index 43c0c90068..f784f05283 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/MessagePollContent.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/MessagePollContent.kt
@@ -24,7 +24,7 @@ import org.matrix.android.sdk.api.session.room.model.relation.RelationDefaultCon
 @JsonClass(generateAdapter = true)
 data class MessagePollContent(
         /**
-         * Local message type, not from server
+         * Local message type, not from server.
          */
         @Transient
         override val msgType: String = MessageType.MSGTYPE_POLL_START,
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/MessagePollResponseContent.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/MessagePollResponseContent.kt
index 022915ed69..32bfb71090 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/MessagePollResponseContent.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/MessagePollResponseContent.kt
@@ -24,7 +24,7 @@ import org.matrix.android.sdk.api.session.room.model.relation.RelationDefaultCon
 @JsonClass(generateAdapter = true)
 data class MessagePollResponseContent(
         /**
-         * Local message type, not from server
+         * Local message type, not from server.
          */
         @Transient
         override val msgType: String = MessageType.MSGTYPE_POLL_RESPONSE,
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/MessageStickerContent.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/MessageStickerContent.kt
index 3d774cadb2..f8c1c0d798 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/MessageStickerContent.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/MessageStickerContent.kt
@@ -25,7 +25,7 @@ import org.matrix.android.sdk.api.session.room.model.relation.RelationDefaultCon
 @JsonClass(generateAdapter = true)
 data class MessageStickerContent(
         /**
-         * Set in local, not from server
+         * Set in local, not from server.
          */
         @Transient
         override val msgType: String = MessageType.MSGTYPE_STICKER_LOCAL,
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/MessageType.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/MessageType.kt
index 106bf2e030..b12d9ed6c8 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/MessageType.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/MessageType.kt
@@ -41,6 +41,6 @@ object MessageType {
     const val MSGTYPE_SNOWFALL = "io.element.effect.snowfall"
 
     // Fake message types for live location events to be able to inherit them from MessageContent
-    const val MSGTYPE_LIVE_LOCATION_STATE = "org.matrix.android.sdk.livelocation.state"
-    const val MSGTYPE_LIVE_LOCATION = "org.matrix.android.sdk.livelocation"
+    const val MSGTYPE_BEACON_INFO = "org.matrix.android.sdk.beacon.info"
+    const val MSGTYPE_BEACON_LOCATION_DATA = "org.matrix.android.sdk.beacon.location.data"
 }
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/MessageVerificationKeyContent.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/MessageVerificationKeyContent.kt
index 1a15e056ab..a6b36ce6cb 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/MessageVerificationKeyContent.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/MessageVerificationKeyContent.kt
@@ -26,7 +26,7 @@ import org.matrix.android.sdk.internal.crypto.verification.VerificationInfoKeyFa
 @JsonClass(generateAdapter = true)
 internal data class MessageVerificationKeyContent(
         /**
-         * The device’s ephemeral public key, as an unpadded base64 string
+         * The device’s ephemeral public key, as an unpadded base64 string.
          */
         @Json(name = "key") override val key: String? = null,
         @Json(name = "m.relates_to") val relatesTo: RelationDefaultContent?
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/MessageVerificationRequestContent.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/MessageVerificationRequestContent.kt
index b2b3cdac90..a0699831f7 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/MessageVerificationRequestContent.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/MessageVerificationRequestContent.kt
@@ -24,7 +24,7 @@ import org.matrix.android.sdk.internal.crypto.verification.VerificationInfoReque
 
 @JsonClass(generateAdapter = true)
 data class MessageVerificationRequestContent(
-        @Json(name = MessageContent.MSG_TYPE_JSON_KEY)override val msgType: String = MessageType.MSGTYPE_VERIFICATION_REQUEST,
+        @Json(name = MessageContent.MSG_TYPE_JSON_KEY) override val msgType: String = MessageType.MSGTYPE_VERIFICATION_REQUEST,
         @Json(name = "body") override val body: String,
         @Json(name = "from_device") override val fromDevice: String?,
         @Json(name = "methods") override val methods: List,
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/MessageVideoContent.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/MessageVideoContent.kt
index 9266a0fb0f..9b657971b9 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/MessageVideoContent.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/MessageVideoContent.kt
@@ -27,7 +27,7 @@ data class MessageVideoContent(
         /**
          * Required. Must be 'm.video'.
          */
-        @Json(name = MessageContent.MSG_TYPE_JSON_KEY)override val msgType: String,
+        @Json(name = MessageContent.MSG_TYPE_JSON_KEY) override val msgType: String,
 
         /**
          * Required. A description of the video e.g. 'Gangnam style', or some kind of content description for accessibility e.g. 'video attachment'.
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/MessageWithAttachmentContent.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/MessageWithAttachmentContent.kt
index 95dfb6b864..8d9dbee6b3 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/MessageWithAttachmentContent.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/MessageWithAttachmentContent.kt
@@ -19,7 +19,7 @@ package org.matrix.android.sdk.api.session.room.model.message
 import org.matrix.android.sdk.api.session.crypto.model.EncryptedFileInfo
 
 /**
- * Interface for message which can contains an encrypted file
+ * Interface for message which can contains an encrypted file.
  */
 interface MessageWithAttachmentContent : MessageContent {
     /**
@@ -36,7 +36,7 @@ interface MessageWithAttachmentContent : MessageContent {
 }
 
 /**
- * Get the url of the encrypted file or of the file
+ * Get the url of the encrypted file or of the file.
  */
 fun MessageWithAttachmentContent.getFileUrl() = encryptedFileInfo?.url ?: url
 
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/VideoInfo.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/VideoInfo.kt
index 28f3a47d11..b9c2472197 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/VideoInfo.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/VideoInfo.kt
@@ -64,8 +64,8 @@ data class VideoInfo(
 )
 
 /**
- * Get the url of the encrypted thumbnail or of the thumbnail
+ * Get the url of the encrypted thumbnail or of the thumbnail.
  */
 fun VideoInfo.getThumbnailUrl(): String? {
-        return thumbnailFile?.url ?: thumbnailUrl
+    return thumbnailFile?.url ?: thumbnailUrl
 }
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/relation/RelationContent.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/relation/RelationContent.kt
index 53b1fea873..01f7425322 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/relation/RelationContent.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/relation/RelationContent.kt
@@ -19,7 +19,7 @@ package org.matrix.android.sdk.api.session.room.model.relation
 import org.matrix.android.sdk.api.session.events.model.RelationType
 
 interface RelationContent {
-    /** See [RelationType] for known possible values */
+    /** See [RelationType] for known possible values. */
     val type: String?
     val eventId: String?
     val inReplyTo: ReplyToContent?
@@ -27,7 +27,7 @@ interface RelationContent {
 
     /**
      * This flag indicates that the message should be rendered as a reply
-     * fallback, when isFallingBack = false
+     * fallback, when isFallingBack = false.
      */
     val isFallingBack: Boolean?
 }
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/relation/RelationService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/relation/RelationService.kt
index 4409898908..0d094b835b 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/relation/RelationService.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/relation/RelationService.kt
@@ -82,7 +82,7 @@ interface RelationService {
                  options: List): Cancelable
 
     /**
-     * Edit a text message body. Limited to "m.text" contentType
+     * Edit a text message body. Limited to "m.text" contentType.
      * @param targetEvent The event to edit
      * @param newBodyText The edited body
      * @param compatibilityBodyText The text that will appear on clients that don't support yet edition
@@ -107,7 +107,7 @@ interface RelationService {
                   compatibilityBodyText: String = "* $newBodyText"): Cancelable
 
     /**
-     * Get the edit history of the given event
+     * Get the edit history of the given event.
      * The return list will contain the original event and all the editions of this event, done by the
      * same sender, sorted in the reverse order (so the original event is the latest element, and the
      * latest edition is the first element of the list)
@@ -133,21 +133,21 @@ interface RelationService {
     ): Cancelable?
 
     /**
-     * Get the current EventAnnotationsSummary
+     * Get the current EventAnnotationsSummary.
      * @param eventId the eventId to look for EventAnnotationsSummary
      * @return the EventAnnotationsSummary found
      */
     fun getEventAnnotationsSummary(eventId: String): EventAnnotationsSummary?
 
     /**
-     * Get a LiveData of EventAnnotationsSummary for the specified eventId
+     * Get a LiveData of EventAnnotationsSummary for the specified eventId.
      * @param eventId the eventId to look for EventAnnotationsSummary
      * @return the LiveData of EventAnnotationsSummary
      */
     fun getEventAnnotationsSummaryLive(eventId: String): LiveData>
 
     /**
-     * Creates a thread reply for an existing timeline event
+     * Creates a thread reply for an existing timeline event.
      * The replyInThreadText can be a Spannable and contains special spans (MatrixItemSpan) that will be translated
      * by the sdk into pills.
      * @param rootThreadEventId the root thread eventId
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/roomdirectory/PublicRoom.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/roomdirectory/PublicRoom.kt
index 01f5d9cde8..2033f366ae 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/roomdirectory/PublicRoom.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/roomdirectory/PublicRoom.kt
@@ -79,13 +79,13 @@ data class PublicRoom(
         val avatarUrl: String? = null,
 
         /**
-         * Undocumented item
+         * Undocumented item.
          */
         @Json(name = "m.federate")
         val isFederated: Boolean = false
 ) {
     /**
-     * Return the canonical alias, or the first alias from the list of aliases, or null
+     * Return the canonical alias, or the first alias from the list of aliases, or null.
      */
     fun getPrimaryAlias(): String? {
         return canonicalAlias ?: aliases?.firstOrNull()
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/roomdirectory/PublicRoomsFilter.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/roomdirectory/PublicRoomsFilter.kt
index 66ebc59464..cc0ce669b9 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/roomdirectory/PublicRoomsFilter.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/roomdirectory/PublicRoomsFilter.kt
@@ -19,7 +19,7 @@ import com.squareup.moshi.Json
 import com.squareup.moshi.JsonClass
 
 /**
- * Class to define a filter to retrieve public rooms
+ * Class to define a filter to retrieve public rooms.
  */
 @JsonClass(generateAdapter = true)
 data class PublicRoomsFilter(
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/roomdirectory/PublicRoomsParams.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/roomdirectory/PublicRoomsParams.kt
index 3af354a01d..c4227b5767 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/roomdirectory/PublicRoomsParams.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/roomdirectory/PublicRoomsParams.kt
@@ -19,7 +19,7 @@ import com.squareup.moshi.Json
 import com.squareup.moshi.JsonClass
 
 /**
- * Class to pass parameters to get the public rooms list
+ * Class to pass parameters to get the public rooms list.
  */
 @JsonClass(generateAdapter = true)
 data class PublicRoomsParams(
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/roomdirectory/PublicRoomsResponse.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/roomdirectory/PublicRoomsResponse.kt
index 82da8bc49b..5a33ba3cdf 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/roomdirectory/PublicRoomsResponse.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/roomdirectory/PublicRoomsResponse.kt
@@ -19,7 +19,7 @@ import com.squareup.moshi.Json
 import com.squareup.moshi.JsonClass
 
 /**
- * Class representing the public rooms request response
+ * Class representing the public rooms request response.
  */
 @JsonClass(generateAdapter = true)
 data class PublicRoomsResponse(
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/thirdparty/ThirdPartyProtocolInstance.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/thirdparty/ThirdPartyProtocolInstance.kt
index 0ca0444589..6a2dc47650 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/thirdparty/ThirdPartyProtocolInstance.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/thirdparty/ThirdPartyProtocolInstance.kt
@@ -46,13 +46,13 @@ data class ThirdPartyProtocolInstance(
         val networkId: String? = null,
 
         /**
-         * FIXDOC Not documented on matrix.org doc
+         * FIXDOC Not documented on matrix.org doc.
          */
         @Json(name = "instance_id")
         val instanceId: String? = null,
 
         /**
-         * FIXDOC Not documented on matrix.org doc
+         * FIXDOC Not documented on matrix.org doc.
          */
         @Json(name = "bot_user_id")
         val botUserId: String? = null
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/tombstone/RoomTombstoneContent.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/tombstone/RoomTombstoneContent.kt
index 9b607aa712..d0a976cd97 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/tombstone/RoomTombstoneContent.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/tombstone/RoomTombstoneContent.kt
@@ -19,7 +19,7 @@ import com.squareup.moshi.Json
 import com.squareup.moshi.JsonClass
 
 /**
- * Class to contains Tombstone information
+ * Class to contains Tombstone information.
  */
 @JsonClass(generateAdapter = true)
 data class RoomTombstoneContent(
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/notification/RoomNotificationState.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/notification/RoomNotificationState.kt
index ea59ff4d48..919aed63eb 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/notification/RoomNotificationState.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/notification/RoomNotificationState.kt
@@ -17,26 +17,26 @@
 package org.matrix.android.sdk.api.session.room.notification
 
 /**
- * Defines the room notification state
+ * Defines the room notification state.
  */
 enum class RoomNotificationState {
     /**
-     * All the messages will trigger a noisy notification
+     * All the messages will trigger a noisy notification.
      */
     ALL_MESSAGES_NOISY,
 
     /**
-     * All the messages will trigger a notification
+     * All the messages will trigger a notification.
      */
     ALL_MESSAGES,
 
     /**
-     * Only the messages with user display name / user name will trigger notifications
+     * Only the messages with user display name / user name will trigger notifications.
      */
     MENTIONS_ONLY,
 
     /**
-     * No notifications
+     * No notifications.
      */
     MUTE
 }
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/powerlevels/PowerLevelsHelper.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/powerlevels/PowerLevelsHelper.kt
index 99139723a8..165a912b7f 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/powerlevels/PowerLevelsHelper.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/powerlevels/PowerLevelsHelper.kt
@@ -32,7 +32,7 @@ import org.matrix.android.sdk.api.session.room.model.usersDefaultOrDefault
 class PowerLevelsHelper(private val powerLevelsContent: PowerLevelsContent) {
 
     /**
-     * Returns the user power level of a dedicated user Id
+     * Returns the user power level of a dedicated user Id.
      *
      * @param userId the user id
      * @return the power level
@@ -44,7 +44,7 @@ class PowerLevelsHelper(private val powerLevelsContent: PowerLevelsContent) {
     }
 
     /**
-     * Returns the user power level of a dedicated user Id
+     * Returns the user power level of a dedicated user Id.
      *
      * @param userId the user id
      * @return the power level
@@ -56,7 +56,7 @@ class PowerLevelsHelper(private val powerLevelsContent: PowerLevelsContent) {
     }
 
     /**
-     * Tell if an user can send an event of a certain type
+     * Tell if an user can send an event of a certain type.
      *
      * @param userId  the id of the user to check for.
      * @param isState true if the event is a state event (ie. state key is not null)
@@ -77,7 +77,7 @@ class PowerLevelsHelper(private val powerLevelsContent: PowerLevelsContent) {
     }
 
     /**
-     * Check if the user have the necessary power level to invite
+     * Check if the user have the necessary power level to invite.
      * @param userId the id of the user to check for.
      * @return true if able to invite
      */
@@ -87,7 +87,7 @@ class PowerLevelsHelper(private val powerLevelsContent: PowerLevelsContent) {
     }
 
     /**
-     * Check if the user have the necessary power level to ban
+     * Check if the user have the necessary power level to ban.
      * @param userId the id of the user to check for.
      * @return true if able to ban
      */
@@ -97,7 +97,7 @@ class PowerLevelsHelper(private val powerLevelsContent: PowerLevelsContent) {
     }
 
     /**
-     * Check if the user have the necessary power level to kick
+     * Check if the user have the necessary power level to kick (remove).
      * @param userId the id of the user to check for.
      * @return true if able to kick
      */
@@ -107,7 +107,7 @@ class PowerLevelsHelper(private val powerLevelsContent: PowerLevelsContent) {
     }
 
     /**
-     * Check if the user have the necessary power level to redact
+     * Check if the user have the necessary power level to redact.
      * @param userId the id of the user to check for.
      * @return true if able to redact
      */
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/read/ReadService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/read/ReadService.kt
index b037a3f366..036628c02f 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/read/ReadService.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/read/ReadService.kt
@@ -62,7 +62,7 @@ interface ReadService {
     fun getMyReadReceiptLive(): LiveData>
 
     /**
-     * Get the eventId where the read receipt for the provided user is
+     * Get the eventId where the read receipt for the provided user is.
      * @param userId the id of the user to look for
      *
      * @return the eventId where the read receipt for the provided user is attached, or null if not found
@@ -70,7 +70,7 @@ interface ReadService {
     fun getUserReadReceipt(userId: String): String?
 
     /**
-     * Returns a live list of read receipts for a given event
+     * Returns a live list of read receipts for a given event.
      * @param eventId: the event
      */
     fun getEventReadReceiptsLive(eventId: String): LiveData>
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/send/DraftService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/send/DraftService.kt
index a9481d71a2..e03c89a12a 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/send/DraftService.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/send/DraftService.kt
@@ -22,22 +22,22 @@ import org.matrix.android.sdk.api.util.Optional
 interface DraftService {
 
     /**
-     * Save or update a draft to the room
+     * Save or update a draft to the room.
      */
     suspend fun saveDraft(draft: UserDraft)
 
     /**
-     * Delete the last draft, basically just after sending the message
+     * Delete the last draft, basically just after sending the message.
      */
     suspend fun deleteDraft()
 
     /**
-     * Return the current draft or null
+     * Return the current draft or null.
      */
     fun getDraft(): UserDraft?
 
     /**
-     * Return the current draft if any, as a live data
+     * Return the current draft if any, as a live data.
      */
     fun getDraftLive(): LiveData>
 }
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/send/SendService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/send/SendService.kt
index af7ab11df1..4bb8abef8a 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/send/SendService.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/send/SendService.kt
@@ -126,19 +126,19 @@ interface SendService {
     fun redactEvent(event: Event, reason: String?): Cancelable
 
     /**
-     * Schedule this message to be resent
+     * Schedule this message to be resent.
      * @param localEcho the unsent local echo
      */
     fun resendTextMessage(localEcho: TimelineEvent): Cancelable
 
     /**
-     * Schedule this message to be resent
+     * Schedule this message to be resent.
      * @param localEcho the unsent local echo
      */
     fun resendMediaMessage(localEcho: TimelineEvent): Cancelable
 
     /**
-     * Send a location event to the room
+     * Send a location event to the room.
      * @param latitude required latitude of the location
      * @param longitude required longitude of the location
      * @param uncertainty Accuracy of the location in meters
@@ -156,23 +156,23 @@ interface SendService {
     fun sendLiveLocation(beaconInfoEventId: String, latitude: Double, longitude: Double, uncertainty: Double?): Cancelable
 
     /**
-     * Remove this failed message from the timeline
+     * Remove this failed message from the timeline.
      * @param localEcho the unsent local echo
      */
     fun deleteFailedEcho(localEcho: TimelineEvent)
 
     /**
-     * Cancel sending a specific event. It has to be in one of the sending states
+     * Cancel sending a specific event. It has to be in one of the sending states.
      */
     fun cancelSend(eventId: String)
 
     /**
-     * Resend all failed messages one by one (and keep order)
+     * Resend all failed messages one by one (and keep order).
      */
     fun resendAllFailedMessages()
 
     /**
-     * Cancel all failed messages
+     * Cancel all failed messages.
      */
     fun cancelAllFailedMessages()
 }
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/send/UserDraft.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/send/UserDraft.kt
index a8c0de2fa5..4ede1a66fc 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/send/UserDraft.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/send/UserDraft.kt
@@ -21,7 +21,7 @@ package org.matrix.android.sdk.api.session.room.send
  * REGULAR: draft of a classical message
  * QUOTE: draft of a message which quotes another message
  * EDIT: draft of an edition of a message
- * REPLY: draft of a reply of another message
+ * REPLY: draft of a reply of another message.
  */
 sealed interface UserDraft {
     data class Regular(val content: String) : UserDraft
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/sender/SenderInfo.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/sender/SenderInfo.kt
index 9b73136fc3..4c308c355a 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/sender/SenderInfo.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/sender/SenderInfo.kt
@@ -21,7 +21,7 @@ import org.matrix.android.sdk.internal.util.replaceSpaceChars
 data class SenderInfo(
         val userId: String,
         /**
-         * Consider using [disambiguatedDisplayName]
+         * Consider using [disambiguatedDisplayName].
          */
         val displayName: String?,
         val isUniqueDisplayName: Boolean,
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/state/StateService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/state/StateService.kt
index f645f3ebf9..f6b56128d3 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/state/StateService.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/state/StateService.kt
@@ -30,81 +30,82 @@ import org.matrix.android.sdk.api.util.Optional
 interface StateService {
 
     /**
-     * Update the topic of the room
+     * Update the topic of the room.
      */
     suspend fun updateTopic(topic: String)
 
     /**
-     * Update the name of the room
+     * Update the name of the room.
      */
     suspend fun updateName(name: String)
 
     /**
-     * Update the canonical alias of the room
+     * Update the canonical alias of the room.
      * @param alias the canonical alias, or null to reset the canonical alias of this room
      * @param altAliases the alternative aliases for this room. It should include the canonical alias if any.
      */
     suspend fun updateCanonicalAlias(alias: String?, altAliases: List)
 
     /**
-     * Update the history readability of the room
+     * Update the history readability of the room.
      */
     suspend fun updateHistoryReadability(readability: RoomHistoryVisibility)
 
     /**
-     * Update the join rule and/or the guest access
+     * Update the join rule and/or the guest access.
      */
     suspend fun updateJoinRule(joinRules: RoomJoinRules?, guestAccess: GuestAccess?, allowList: List? = null)
 
     /**
-     * Update the avatar of the room
+     * Update the avatar of the room.
      */
     suspend fun updateAvatar(avatarUri: Uri, fileName: String)
 
     /**
-     * Delete the avatar of the room
+     * Delete the avatar of the room.
      */
     suspend fun deleteAvatar()
 
     /**
-     * Stops sharing live location in the room
+     * Stops sharing live location in the room.
      * @param userId user id
      */
     suspend fun stopLiveLocation(userId: String)
 
     /**
-     * Returns beacon info state event of a user
+     * Returns beacon info state event of a user.
      * @param userId user id who is sharing location
      * @param filterOnlyLive filters only ongoing live location sharing beacons if true else ended event is included
      */
     suspend fun getLiveLocationBeaconInfo(userId: String, filterOnlyLive: Boolean): Event?
 
     /**
-     * Send a state event to the room
+     * Send a state event to the room.
      * @param eventType The type of event to send.
      * @param stateKey The state_key for the state to send. Can be an empty string.
      * @param body The content object of the event; the fields in this object will vary depending on the type of event
+     * @return the id of the created state event
      */
-    suspend fun sendStateEvent(eventType: String, stateKey: String, body: JsonDict)
+    suspend fun sendStateEvent(eventType: String, stateKey: String, body: JsonDict): String
 
     /**
-     * Get a state event of the room
+     * Get a state event of the room.
      */
     fun getStateEvent(eventType: String, stateKey: QueryStringValue = QueryStringValue.NoCondition): Event?
 
     /**
-     * Get a live state event of the room
+     * Get a live state event of the room.
      */
     fun getStateEventLive(eventType: String, stateKey: QueryStringValue = QueryStringValue.NoCondition): LiveData>
 
     /**
-     * Get state events of the room
+     * Get state events of the room.
      * @param eventTypes Set of eventType. If empty, all state events will be returned
      */
     fun getStateEvents(eventTypes: Set, stateKey: QueryStringValue = QueryStringValue.NoCondition): List
 
     /**
-     * Get live state events of the room
+     * Get live state events of the room.
      * @param eventTypes Set of eventType to observe. If empty, all state events will be observed
      */
     fun getStateEventsLive(eventTypes: Set, stateKey: QueryStringValue = QueryStringValue.NoCondition): LiveData>
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/state/StateServiceExtension.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/state/StateServiceExtension.kt
index c625a7f088..9e45fc126d 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/state/StateServiceExtension.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/state/StateServiceExtension.kt
@@ -23,7 +23,7 @@ import org.matrix.android.sdk.api.session.room.model.RoomJoinRules
 import org.matrix.android.sdk.api.session.room.model.RoomJoinRulesContent
 
 /**
- * Return true if a room can be joined by anyone (RoomJoinRules.PUBLIC)
+ * Return true if a room can be joined by anyone (RoomJoinRules.PUBLIC).
  */
 fun StateService.isPublic(): Boolean {
     return getStateEvent(EventType.STATE_ROOM_JOIN_RULES, QueryStringValue.NoCondition)
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/tags/TagsService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/tags/TagsService.kt
index 69fde61f90..b6b82d8404 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/tags/TagsService.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/tags/TagsService.kt
@@ -21,12 +21,12 @@ package org.matrix.android.sdk.api.session.room.tags
  */
 interface TagsService {
     /**
-     * Add a tag to a room
+     * Add a tag to a room.
      */
     suspend fun addTag(tag: String, order: Double?)
 
     /**
-     * Remove tag from a room
+     * Remove tag from a room.
      */
     suspend fun deleteTag(tag: String)
 }
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/threads/ThreadsService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/threads/ThreadsService.kt
index 839cdff63b..9587be68f1 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/threads/ThreadsService.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/threads/ThreadsService.kt
@@ -28,24 +28,24 @@ import org.matrix.android.sdk.api.session.room.threads.model.ThreadSummary
 interface ThreadsService {
 
     /**
-     * Returns a [LiveData] list of all the [ThreadSummary] that exists at the room level
+     * Returns a [LiveData] list of all the [ThreadSummary] that exists at the room level.
      */
     fun getAllThreadSummariesLive(): LiveData>
 
     /**
-     * Returns a list of all the [ThreadSummary] that exists at the room level
+     * Returns a list of all the [ThreadSummary] that exists at the room level.
      */
     fun getAllThreadSummaries(): List
 
     /**
      * Enhance the provided ThreadSummary[List] by adding the latest
-     * message edition for that thread
+     * message edition for that thread.
      * @return the enhanced [List] with edited updates
      */
     fun enhanceThreadWithEditions(threads: List): List
 
     /**
-     * Fetch all thread replies for the specified thread using the /relations api
+     * Fetch all thread replies for the specified thread using the /relations api.
      * @param rootThreadEventId the root thread eventId
      * @param from defines the token that will fetch from that position
      * @param limit defines the number of max results the api will respond with
@@ -53,7 +53,7 @@ interface ThreadsService {
     suspend fun fetchThreadTimeline(rootThreadEventId: String, from: String, limit: Int)
 
     /**
-     * Fetch all thread summaries for the current room using the enhanced /messages api
+     * Fetch all thread summaries for the current room using the enhanced /messages api.
      */
     suspend fun fetchThreadSummaries()
 }
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/threads/local/ThreadsLocalService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/threads/local/ThreadsLocalService.kt
index f7b379e382..b5cef3c62b 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/threads/local/ThreadsLocalService.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/threads/local/ThreadsLocalService.kt
@@ -27,34 +27,34 @@ import org.matrix.android.sdk.api.session.room.timeline.TimelineEvent
 interface ThreadsLocalService {
 
     /**
-     * Returns a [LiveData] list of all the thread root TimelineEvents that exists at the room level
+     * Returns a [LiveData] list of all the thread root TimelineEvents that exists at the room level.
      */
     fun getAllThreadsLive(): LiveData>
 
     /**
-     * Returns a list of all the thread root TimelineEvents that exists at the room level
+     * Returns a list of all the thread root TimelineEvents that exists at the room level.
      */
     fun getAllThreads(): List
 
     /**
-     * Returns a [LiveData] list of all the marked unread threads that exists at the room level
+     * Returns a [LiveData] list of all the marked unread threads that exists at the room level.
      */
     fun getMarkedThreadNotificationsLive(): LiveData>
 
     /**
-     * Returns a list of all the marked unread threads that exists at the room level
+     * Returns a list of all the marked unread threads that exists at the room level.
      */
     fun getMarkedThreadNotifications(): List
 
     /**
-     * Returns whether or not the current user is participating in the thread
-     * @param rootThreadEventId the eventId of the current thread
+     * Returns whether or not the current user is participating in the thread.
+     * @param rootThreadEventId the eventId of the current thread.
      */
     fun isUserParticipatingInThread(rootThreadEventId: String): Boolean
 
     /**
      * Enhance the provided root thread TimelineEvent [List] by adding the latest
-     * message edition for that thread
+     * message edition for that thread.
      * @return the enhanced [List] with edited updates
      */
     fun mapEventsWithEdition(threads: List): List
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/threads/model/ThreadSummary.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/threads/model/ThreadSummary.kt
index 017afba1ba..1ef972e889 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/threads/model/ThreadSummary.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/threads/model/ThreadSummary.kt
@@ -20,7 +20,7 @@ import org.matrix.android.sdk.api.session.events.model.Event
 import org.matrix.android.sdk.api.session.room.sender.SenderInfo
 
 /**
- * The main thread Summary model, mainly used to display the thread list
+ * The main thread Summary model, mainly used to display the thread list.
  */
 data class ThreadSummary(val roomId: String,
                          val rootEvent: Event?,
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/timeline/Timeline.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/timeline/Timeline.kt
index d47a656798..1824d5dc6c 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/timeline/Timeline.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/timeline/Timeline.kt
@@ -100,23 +100,23 @@ interface Timeline {
         fun onTimelineUpdated(snapshot: List) = Unit
 
         /**
-         * Called whenever an error we can't recover from occurred
+         * Called whenever an error we can't recover from occurred.
          */
         fun onTimelineFailure(throwable: Throwable) = Unit
 
         /**
-         * Called when new events come through the sync
+         * Called when new events come through the sync.
          */
         fun onNewTimelineEvents(eventIds: List) = Unit
 
         /**
-         * Called when the pagination state has changed in one direction
+         * Called when the pagination state has changed in one direction.
          */
         fun onStateUpdated(direction: Direction, state: PaginationState) = Unit
     }
 
     /**
-     * Pagination state
+     * Pagination state.
      */
     data class PaginationState(
             val hasMoreToLoad: Boolean = true,
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/timeline/TimelineEvent.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/timeline/TimelineEvent.kt
index a2ae8bfeb5..b87bc25435 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/timeline/TimelineEvent.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/timeline/TimelineEvent.kt
@@ -29,7 +29,7 @@ import org.matrix.android.sdk.api.session.events.model.isSticker
 import org.matrix.android.sdk.api.session.events.model.toModel
 import org.matrix.android.sdk.api.session.room.model.EventAnnotationsSummary
 import org.matrix.android.sdk.api.session.room.model.ReadReceipt
-import org.matrix.android.sdk.api.session.room.model.livelocation.LiveLocationBeaconContent
+import org.matrix.android.sdk.api.session.room.model.message.MessageBeaconInfoContent
 import org.matrix.android.sdk.api.session.room.model.message.MessageContent
 import org.matrix.android.sdk.api.session.room.model.message.MessagePollContent
 import org.matrix.android.sdk.api.session.room.model.message.MessageStickerContent
@@ -47,7 +47,7 @@ import org.matrix.android.sdk.api.util.ContentUtils.extractUsefulTextFromReply
 data class TimelineEvent(
         val root: Event,
         /**
-         * Uniquely identify an event, computed locally by the sdk
+         * Uniquely identify an event, computed locally by the sdk.
          */
         val localId: Long,
         val eventId: String,
@@ -103,12 +103,12 @@ data class TimelineEvent(
 }
 
 /**
- * Tells if the event has been edited
+ * Tells if the event has been edited.
  */
 fun TimelineEvent.hasBeenEdited() = annotations?.editSummary != null
 
 /**
- * Get the latest known eventId for an edited event, or the eventId for an Event which has not been edited
+ * Get the latest known eventId for an edited event, or the eventId for an Event which has not been edited.
  */
 fun TimelineEvent.getLatestEventId(): String {
     return annotations
@@ -119,33 +119,33 @@ fun TimelineEvent.getLatestEventId(): String {
 }
 
 /**
- * Get the relation content if any
+ * Get the relation content if any.
  */
 fun TimelineEvent.getRelationContent(): RelationDefaultContent? {
     return root.getRelationContent()
 }
 
 /**
- * Get the eventId which was edited by this event if any
+ * Get the eventId which was edited by this event if any.
  */
 fun TimelineEvent.getEditedEventId(): String? {
     return getRelationContent()?.takeIf { it.type == RelationType.REPLACE }?.eventId
 }
 
 /**
- * Get last MessageContent, after a possible edition
+ * Get last MessageContent, after a possible edition.
  */
 fun TimelineEvent.getLastMessageContent(): MessageContent? {
     return when (root.getClearType()) {
         EventType.STICKER                   -> root.getClearContent().toModel()
         in EventType.POLL_START             -> (annotations?.editSummary?.latestContent ?: root.getClearContent()).toModel()
-        in EventType.STATE_ROOM_BEACON_INFO -> (annotations?.editSummary?.latestContent ?: root.getClearContent()).toModel()
+        in EventType.STATE_ROOM_BEACON_INFO -> (annotations?.editSummary?.latestContent ?: root.getClearContent()).toModel()
         else                                -> (annotations?.editSummary?.latestContent ?: root.getClearContent()).toModel()
     }
 }
 
 /**
- * Returns true if it's a reply
+ * Returns true if it's a reply.
  */
 fun TimelineEvent.isReply(): Boolean {
     return root.isReply()
@@ -163,14 +163,14 @@ fun TimelineEvent.isSticker(): Boolean {
 }
 
 /**
- * Returns whether or not the event is a root thread event
+ * Returns whether or not the event is a root thread event.
  */
 fun TimelineEvent.isRootThread(): Boolean {
     return root.threadDetails?.isRootThread.orFalse()
 }
 
 /**
- * Get the latest message body, after a possible edition, stripping the reply prefix if necessary
+ * Get the latest message body, after a possible edition, stripping the reply prefix if necessary.
  */
 fun TimelineEvent.getTextEditableContent(): String {
     val lastContentBody = getLastMessageContent()?.body ?: return ""
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/timeline/TimelineEventFilters.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/timeline/TimelineEventFilters.kt
index 4415c8e4b3..0f0c15b613 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/timeline/TimelineEventFilters.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/timeline/TimelineEventFilters.kt
@@ -16,21 +16,22 @@
 
 package org.matrix.android.sdk.api.session.room.timeline
 
+// TODO Move to internal, strange?
 data class TimelineEventFilters(
         /**
-         * A flag to filter edit events
+         * A flag to filter edit events.
          */
         val filterEdits: Boolean = false,
         /**
-         * A flag to filter redacted events
+         * A flag to filter redacted events.
          */
         val filterRedacted: Boolean = false,
         /**
-         * A flag to filter useless events, such as membership events without any change
+         * A flag to filter useless events, such as membership events without any change.
          */
         val filterUseless: Boolean = false,
         /**
-         * A flag to filter by types. It should be used with [allowedTypes] field
+         * A flag to filter by types. It should be used with [allowedTypes] field.
          */
         val filterTypes: Boolean = false,
         /**
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/timeline/TimelineSettings.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/timeline/TimelineSettings.kt
index 6548453c8a..fd6732d0d1 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/timeline/TimelineSettings.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/timeline/TimelineSettings.kt
@@ -29,12 +29,17 @@ data class TimelineSettings(
          */
         val buildReadReceipts: Boolean = true,
         /**
-         * The root thread eventId if this is a thread timeline, or null if this is NOT a thread timeline
+         * The root thread eventId if this is a thread timeline, or null if this is NOT a thread timeline.
          */
-        val rootThreadEventId: String? = null) {
+        val rootThreadEventId: String? = null,
+        /**
+         * If true Sender Info shown in room will get the latest data information (avatar + displayName).
+         */
+        val useLiveSenderInfo: Boolean = false,
+) {
 
     /**
-     * Returns true if this is a thread timeline or false otherwise
+     * Returns true if this is a thread timeline or false otherwise.
      */
     fun isThreadTimeline() = rootThreadEventId != null
 }
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/typing/TypingService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/typing/TypingService.kt
index e69afa4fc8..a462d5cba7 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/typing/TypingService.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/typing/TypingService.kt
@@ -22,7 +22,7 @@ package org.matrix.android.sdk.api.session.room.typing
 interface TypingService {
 
     /**
-     * To call when user is typing a message in the room
+     * To call when user is typing a message in the room.
      * The SDK will handle the requests scheduling to the homeserver:
      * - No more than one typing request per 10s
      * - If not called after 10s, the SDK will notify the homeserver that the user is not typing anymore
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/uploads/UploadsService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/uploads/UploadsService.kt
index e2462d007d..b6fec9cd51 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/uploads/UploadsService.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/uploads/UploadsService.kt
@@ -22,7 +22,7 @@ package org.matrix.android.sdk.api.session.room.uploads
 interface UploadsService {
 
     /**
-     * Get a list of events containing URL sent to a room, from most recent to oldest one
+     * Get a list of events containing URL sent to a room, from most recent to oldest one.
      * @param numberOfEvents the expected number of events to retrieve. The result can contain less events.
      * @param since token to get next page, or null to get the first page
      */
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/version/RoomVersionService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/version/RoomVersionService.kt
index d806e6007e..4b6c907af0 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/version/RoomVersionService.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/version/RoomVersionService.kt
@@ -18,28 +18,28 @@ package org.matrix.android.sdk.api.session.room.version
 
 interface RoomVersionService {
     /**
-     * Return the room version of this room
+     * Return the room version of this room.
      */
     fun getRoomVersion(): String
 
     /**
-     * Upgrade to the given room version
+     * Upgrade to the given room version.
      * @return the replacement room id
      */
     suspend fun upgradeToVersion(version: String): String
 
     /**
-     * Get the recommended room version for the current homeserver
+     * Get the recommended room version for the current homeserver.
      */
     fun getRecommendedVersion(): String
 
     /**
-     * Ask if the user has enough power level to upgrade the room
+     * Ask if the user has enough power level to upgrade the room.
      */
     fun userMayUpgradeRoom(userId: String): Boolean
 
     /**
-     * Return true if the current room version is declared unstable by the homeserver
+     * Return true if the current room version is declared unstable by the homeserver.
      */
     fun isUsingUnstableRoomVersion(): Boolean
 }
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/securestorage/EncryptedSecretContent.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/securestorage/EncryptedSecretContent.kt
index 42682efb12..ddcecb466c 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/securestorage/EncryptedSecretContent.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/securestorage/EncryptedSecretContent.kt
@@ -29,7 +29,7 @@ import org.matrix.android.sdk.internal.session.user.accountdata.AccountDataConte
  */
 @JsonClass(generateAdapter = true)
 data class EncryptedSecretContent(
-        /** unpadded base64-encoded ciphertext */
+        /** unpadded base64-encoded ciphertext. */
         @Json(name = "ciphertext") val ciphertext: String? = null,
         @Json(name = "mac") val mac: String? = null,
         @Json(name = "ephemeral") val ephemeral: String? = null,
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/securestorage/SecretStorageKeyContent.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/securestorage/SecretStorageKeyContent.kt
index f7725be007..e7f872e400 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/securestorage/SecretStorageKeyContent.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/securestorage/SecretStorageKeyContent.kt
@@ -51,7 +51,7 @@ data class KeyInfo(
 
 @JsonClass(generateAdapter = true)
 data class SecretStorageKeyContent(
-        /** Currently support m.secret_storage.v1.curve25519-aes-sha2 */
+        /** Currently support m.secret_storage.v1.curve25519-aes-sha2. */
         @Json(name = "algorithm") val algorithm: String? = null,
         @Json(name = "name") val name: String? = null,
         @Json(name = "passphrase") val passphrase: SsssPassphrase? = null,
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/securestorage/SharedSecretStorageError.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/securestorage/SharedSecretStorageError.kt
index a91b97b86c..038533c19e 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/securestorage/SharedSecretStorageError.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/securestorage/SharedSecretStorageError.kt
@@ -23,7 +23,7 @@ sealed class SharedSecretStorageError(message: String?) : Throwable(message) {
     data class UnsupportedAlgorithm(val algorithm: String) : SharedSecretStorageError("Unknown algorithm $algorithm")
     data class SecretNotEncrypted(val secretName: String) : SharedSecretStorageError("Missing content for secret $secretName")
     data class SecretNotEncryptedWithKey(val secretName: String, val keyId: String) :
-        SharedSecretStorageError("Missing content for secret $secretName with key $keyId")
+            SharedSecretStorageError("Missing content for secret $secretName with key $keyId")
 
     object BadKeyFormat : SharedSecretStorageError("Bad Key Format")
     object ParsingError : SharedSecretStorageError("parsing Error")
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/securestorage/SharedSecretStorageService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/securestorage/SharedSecretStorageService.kt
index 721a2bc8af..528e071966 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/securestorage/SharedSecretStorageService.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/securestorage/SharedSecretStorageService.kt
@@ -98,12 +98,12 @@ interface SharedSecretStorageService {
     suspend fun storeSecret(name: String, secretBase64: String, keys: List)
 
     /**
-     * Use this call to determine which SSSSKeySpec to use for requesting secret
+     * Use this call to determine which SSSSKeySpec to use for requesting secret.
      */
     fun getAlgorithmsForSecret(name: String): List
 
     /**
-     * Get an encrypted secret from the shared storage
+     * Get an encrypted secret from the shared storage.
      *
      * @param name The name of the secret
      * @param keyId The id of the key that should be used to decrypt (null for default key)
@@ -113,7 +113,7 @@ interface SharedSecretStorageService {
     suspend fun getSecret(name: String, keyId: String?, secretKey: SsssKeySpec): String
 
     /**
-     * Return true if SSSS is configured
+     * Return true if SSSS is configured.
      */
     fun isRecoverySetup(): Boolean {
         return checkShouldBeAbleToAccessSecrets(
@@ -131,7 +131,7 @@ interface SharedSecretStorageService {
 
     fun checkShouldBeAbleToAccessSecrets(secretNames: List, keyId: String?): IntegrityResult
 
-    fun requestSecret(name: String, myOtherDeviceId: String)
+    suspend fun requestSecret(name: String, myOtherDeviceId: String)
 
     data class KeyRef(
             val keyId: String?,
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/securestorage/SsssKeySpec.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/securestorage/SsssKeySpec.kt
index 03efb9b3db..35a67b0865 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/securestorage/SsssKeySpec.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/securestorage/SsssKeySpec.kt
@@ -20,7 +20,7 @@ import org.matrix.android.sdk.api.listeners.ProgressListener
 import org.matrix.android.sdk.api.session.crypto.keysbackup.extractCurveKeyFromRecoveryKey
 import org.matrix.android.sdk.internal.crypto.keysbackup.deriveKey
 
-/** Tag class */
+/** Tag class. */
 interface SsssKeySpec
 
 data class RawBytesKeySpec(
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/signout/SignOutService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/signout/SignOutService.kt
index 4e4eba274e..d64b2e6e92 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/signout/SignOutService.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/signout/SignOutService.kt
@@ -30,12 +30,12 @@ interface SignOutService {
     suspend fun signInAgain(password: String)
 
     /**
-     * Update the session with credentials received after SSO
+     * Update the session with credentials received after SSO.
      */
     suspend fun updateCredentials(credentials: Credentials)
 
     /**
-     * Sign out, and release the session, clear all the session data, including crypto data
+     * Sign out, and release the session, clear all the session data, including crypto data.
      * @param signOutFromHomeserver true if the sign out request has to be done
      */
     suspend fun signOut(signOutFromHomeserver: Boolean)
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/space/JoinSpaceResult.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/space/JoinSpaceResult.kt
index e8c69977c6..a8a7ec61d7 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/space/JoinSpaceResult.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/space/JoinSpaceResult.kt
@@ -20,7 +20,7 @@ sealed class JoinSpaceResult {
     object Success : JoinSpaceResult()
     data class Fail(val error: Throwable) : JoinSpaceResult()
 
-    /** Success fully joined the space, but failed to join all or some of it's rooms */
+    /** Success fully joined the space, but failed to join all or some of it's rooms. */
     data class PartialSuccess(val failedRooms: Map) : JoinSpaceResult()
 
     fun isSuccess() = this is Success || this is PartialSuccess
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/space/Space.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/space/Space.kt
index f0ed9daac5..c990388628 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/space/Space.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/space/Space.kt
@@ -27,7 +27,7 @@ interface Space {
     val spaceId: String
 
     /**
-     * A current snapshot of [RoomSummary] associated with the space
+     * A current snapshot of [RoomSummary] associated with the space.
      */
     fun spaceSummary(): RoomSummary?
 
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/space/SpaceService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/space/SpaceService.kt
index f4460b7659..8f16b3b9c3 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/space/SpaceService.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/space/SpaceService.kt
@@ -29,13 +29,13 @@ typealias SpaceSummaryQueryParams = RoomSummaryQueryParams
 interface SpaceService {
 
     /**
-     * Create a space asynchronously
+     * Create a space asynchronously.
      * @return the spaceId of the created space
      */
     suspend fun createSpace(params: CreateSpaceParams): String
 
     /**
-     * Just a shortcut for space creation for ease of use
+     * Just a shortcut for space creation for ease of use.
      */
     suspend fun createSpace(name: String,
                             topic: String?,
@@ -44,7 +44,7 @@ interface SpaceService {
                             roomAliasLocalPart: String? = null): String
 
     /**
-     * Get a space from a roomId
+     * Get a space from a roomId.
      * @param spaceId the roomId to look for.
      * @return a space with spaceId or null if room type is not space
      */
@@ -58,7 +58,7 @@ interface SpaceService {
     suspend fun peekSpace(spaceId: String): SpacePeekResult
 
     /**
-     * Get's information of a space by querying the server
+     * Get's information of a space by querying the server.
      * @param suggestedOnly If true, return only child events and rooms where the m.space.child event has suggested: true.
      * @param limit a client-defined limit to the maximum number of rooms to return per page. Must be a non-negative integer.
      * @param from: Optional. Pagination token given to retrieve the next set of rooms. Note that if a pagination token is provided,
@@ -68,7 +68,7 @@ interface SpaceService {
                                    suggestedOnly: Boolean? = null,
                                    limit: Int? = null,
                                    from: String? = null,
-                                   // when paginating, pass back the m.space.child state events
+            // when paginating, pass back the m.space.child state events
                                    knownStateList: List? = null): SpaceHierarchyData
 
     /**
@@ -106,5 +106,8 @@ interface SpaceService {
 
     suspend fun removeSpaceParent(childRoomId: String, parentSpaceId: String)
 
-    fun getRootSpaceSummaries(): List
+    /**
+     * Get the root spaces, i.e. all the spaces which do not have a parent space.
+     */
+    suspend fun getRootSpaceSummaries(): List
 }
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/space/model/SpaceChildContent.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/space/model/SpaceChildContent.kt
index b55f90cef0..215fcd9a54 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/space/model/SpaceChildContent.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/space/model/SpaceChildContent.kt
@@ -20,11 +20,14 @@ import com.squareup.moshi.Json
 import com.squareup.moshi.JsonClass
 
 /**
+ * Example:
+ * 
  *  "content": {
  *      "via": ["example.com"],
  *      "order": "abcd",
  *      "default": true
  *  }
+ * 
. */ @JsonClass(generateAdapter = true) data class SpaceChildContent( @@ -56,7 +59,7 @@ data class SpaceChildContent( ) { /** * Orders which are not strings, or do not consist solely of ascii characters in the range \x20 (space) to \x7F (~), - * or consist of more than 50 characters, are forbidden and should be ignored if received.) + * or consist of more than 50 characters, are forbidden and should be ignored if received.). */ fun validOrder(): String? { return order diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/space/model/SpaceOrderContent.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/space/model/SpaceOrderContent.kt index a8578347c8..47e276414e 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/space/model/SpaceOrderContent.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/space/model/SpaceOrderContent.kt @@ -20,12 +20,15 @@ import com.squareup.moshi.JsonClass import org.matrix.android.sdk.api.MatrixPatterns /** + * Example: + *
  * {
  * "type": "m.space_order",
  *   "content": {
  *       "order": "..."
  *   }
  * }
+ * 
. */ @JsonClass(generateAdapter = true) data class SpaceOrderContent( diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/statistics/StatisticEvent.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/statistics/StatisticEvent.kt index 946792d31e..76755517ce 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/statistics/StatisticEvent.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/statistics/StatisticEvent.kt @@ -17,11 +17,11 @@ package org.matrix.android.sdk.api.session.statistics /** - * Statistic Events. You can subscribe to received such events using [Session.Listener] + * Statistic Events. You can subscribe to received such events using [Session.Listener]. */ sealed interface StatisticEvent { /** - * Initial sync request, response downloading, and treatment (parsing and storage) of response + * Initial sync request, response downloading, and treatment (parsing and storage) of response. */ data class InitialSyncRequest(val requestDurationMs: Int, val downloadDurationMs: Int, @@ -29,7 +29,7 @@ sealed interface StatisticEvent { val nbOfJoinedRooms: Int) : StatisticEvent /** - * Incremental sync event + * Incremental sync event. */ data class SyncTreatment(val durationMs: Int, val afterPause: Boolean, diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/sync/FilterService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/sync/FilterService.kt index 6d1d12f1bc..bc592df474 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/sync/FilterService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/sync/FilterService.kt @@ -22,13 +22,13 @@ interface FilterService { NoFilter, /** - * Filter for Element, will include only known event type + * Filter for Element, will include only known event type. */ ElementFilter } /** - * Configure the filter for the sync + * Configure the filter for the sync. */ fun setFilter(filterPreset: FilterPreset) } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/sync/InitialSyncStrategy.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/sync/InitialSyncStrategy.kt index 461d816ea7..11f1093393 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/sync/InitialSyncStrategy.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/sync/InitialSyncStrategy.kt @@ -20,7 +20,7 @@ var initialSyncStrategy: InitialSyncStrategy = InitialSyncStrategy.Optimized() sealed class InitialSyncStrategy { /** - * Parse the result in its entirety + * Parse the result in its entirety. * Pros: * - Faster to handle parsed data * Cons: @@ -32,21 +32,21 @@ sealed class InitialSyncStrategy { /** * Optimized. - * First store the request result in a file, to avoid doing it again in case of crash + * First store the request result in a file, to avoid doing it again in case of crash. */ data class Optimized( /** - * Limit to reach to decide to split the init sync response into smaller files - * Empiric value: 1 megabytes + * Limit to reach to decide to split the init sync response into smaller files. + * Empiric value: 1 megabytes. */ val minSizeToSplit: Long = 1_048_576, // 1024 * 1024 /** - * Limit per room to reach to decide to store a join room ephemeral Events into a file - * Empiric value: 1 kilobytes + * Limit per room to reach to decide to store a join room ephemeral Events into a file. + * Empiric value: 1 kilobytes. */ val minSizeToStoreInFile: Long = 1024, /** - * Max number of rooms to insert at a time in database (to avoid too much RAM usage) + * Max number of rooms to insert at a time in database (to avoid too much RAM usage). */ val maxRoomsToInsert: Int = 100 ) : InitialSyncStrategy() diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/sync/model/DeviceListResponse.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/sync/model/DeviceListResponse.kt index c05e1e5187..929343e45d 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/sync/model/DeviceListResponse.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/sync/model/DeviceListResponse.kt @@ -19,7 +19,7 @@ package org.matrix.android.sdk.api.session.sync.model import com.squareup.moshi.JsonClass /** - * This class describes the device list response from a sync request + * This class describes the device list response from a sync request. */ @JsonClass(generateAdapter = true) data class DeviceListResponse( diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/sync/model/LazyRoomSyncEphemeral.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/sync/model/LazyRoomSyncEphemeral.kt index 087a5f52dc..5bd7719d01 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/sync/model/LazyRoomSyncEphemeral.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/sync/model/LazyRoomSyncEphemeral.kt @@ -20,6 +20,6 @@ import com.squareup.moshi.JsonClass @JsonClass(generateAdapter = false) sealed class LazyRoomSyncEphemeral { - data class Parsed(val _roomSyncEphemeral: RoomSyncEphemeral) : LazyRoomSyncEphemeral() + data class Parsed(val roomSyncEphemeral: RoomSyncEphemeral) : LazyRoomSyncEphemeral() object Stored : LazyRoomSyncEphemeral() } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/sync/model/RoomSync.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/sync/model/RoomSync.kt index e3d07602c7..e5ac0a39b2 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/sync/model/RoomSync.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/sync/model/RoomSync.kt @@ -48,7 +48,7 @@ data class RoomSync( @Json(name = "unread_notifications") val unreadNotifications: RoomSyncUnreadNotifications? = null, /** - * The room summary + * The room summary. */ @Json(name = "summary") val summary: RoomSyncSummary? = null diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/sync/model/RoomSyncSummary.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/sync/model/RoomSyncSummary.kt index 7216a0c992..050fa119bb 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/sync/model/RoomSyncSummary.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/sync/model/RoomSyncSummary.kt @@ -36,12 +36,12 @@ data class RoomSyncSummary( @Json(name = "m.heroes") val heroes: List = emptyList(), /** - * The number of m.room.members in state 'joined' (including the syncing user) (can be null) + * The number of m.room.members in state 'joined' (including the syncing user) (can be null). */ @Json(name = "m.joined_member_count") val joinedMembersCount: Int? = null, /** - * The number of m.room.members in state 'invited' (can be null) + * The number of m.room.members in state 'invited' (can be null). */ @Json(name = "m.invited_member_count") val invitedMembersCount: Int? = null ) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/sync/model/RoomSyncTimeline.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/sync/model/RoomSyncTimeline.kt index 82d29a52e2..4fc8adb769 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/sync/model/RoomSyncTimeline.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/sync/model/RoomSyncTimeline.kt @@ -30,12 +30,12 @@ data class RoomSyncTimeline( @Json(name = "events") val events: List? = null, /** - * Boolean which tells whether there are more events on the server + * Boolean which tells whether there are more events on the server. */ @Json(name = "limited") val limited: Boolean = false, /** - * If the batch was limited then this is a token that can be supplied to the server to retrieve more events + * If the batch was limited then this is a token that can be supplied to the server to retrieve more events. */ @Json(name = "prev_batch") val prevToken: String? = null ) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/sync/model/SyncResponse.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/sync/model/SyncResponse.kt index d7dff72288..c70964a513 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/sync/model/SyncResponse.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/sync/model/SyncResponse.kt @@ -48,12 +48,12 @@ data class SyncResponse( @Json(name = "rooms") val rooms: RoomsSyncResponse? = null, /** - * Devices list update + * Devices list update. */ @Json(name = "device_lists") val deviceLists: DeviceListResponse? = null, /** - * One time keys management + * One time keys management. */ @Json(name = "device_one_time_keys_count") val deviceOneTimeKeysCount: DeviceOneTimeKeysCountSyncResponse? = null, diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/threads/ThreadNotificationBadgeState.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/threads/ThreadNotificationBadgeState.kt index 8e861e73de..f3e92ad843 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/threads/ThreadNotificationBadgeState.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/threads/ThreadNotificationBadgeState.kt @@ -17,7 +17,7 @@ package org.matrix.android.sdk.api.session.threads /** - * This class defines the state of a thread notification badge + * This class defines the state of a thread notification badge. */ data class ThreadNotificationBadgeState( val numberOfLocalUnreadThreads: Int = 0, diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/threads/ThreadNotificationState.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/threads/ThreadNotificationState.kt index 8566d68aa5..c110802d23 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/threads/ThreadNotificationState.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/threads/ThreadNotificationState.kt @@ -17,7 +17,7 @@ package org.matrix.android.sdk.api.session.threads /** - * This class defines the state of a thread notification + * This class defines the state of a thread notification. */ enum class ThreadNotificationState { diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/threads/ThreadTimelineEvent.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/threads/ThreadTimelineEvent.kt index 7b433566b8..53f89cefab 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/threads/ThreadTimelineEvent.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/threads/ThreadTimelineEvent.kt @@ -20,7 +20,7 @@ import org.matrix.android.sdk.api.session.room.timeline.TimelineEvent /** * This class contains a thread TimelineEvent along with a boolean that - * determines if the current user has participated in that event + * determines if the current user has participated in that event. */ data class ThreadTimelineEvent( val timelineEvent: TimelineEvent, diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/model/OutgoingGossipingRequestState.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/uia/UiaResult.kt similarity index 68% rename from matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/model/OutgoingGossipingRequestState.kt rename to matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/uia/UiaResult.kt index 8c1bdf6768..ee4a180dea 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/model/OutgoingGossipingRequestState.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/uia/UiaResult.kt @@ -1,5 +1,5 @@ /* - * Copyright 2020 The Matrix.org Foundation C.I.C. + * Copyright 2022 The Matrix.org Foundation C.I.C. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,14 +14,10 @@ * limitations under the License. */ -package org.matrix.android.sdk.api.session.crypto.model +package org.matrix.android.sdk.api.session.uia -enum class OutgoingGossipingRequestState { - UNSENT, - SENDING, - SENT, - CANCELLING, - CANCELLED, - FAILED_TO_SEND, - FAILED_TO_CANCEL +enum class UiaResult { + SUCCESS, + FAILURE, + CANCELLED } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/model/GossipingRequestState.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/uia/exceptions/UiaCancelledException.kt similarity index 63% rename from matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/model/GossipingRequestState.kt rename to matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/uia/exceptions/UiaCancelledException.kt index d9a6f4fcba..d5f9d72efe 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/model/GossipingRequestState.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/uia/exceptions/UiaCancelledException.kt @@ -1,5 +1,5 @@ /* - * Copyright 2020 The Matrix.org Foundation C.I.C. + * Copyright 2022 The Matrix.org Foundation C.I.C. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,18 +14,6 @@ * limitations under the License. */ -package org.matrix.android.sdk.api.session.crypto.model +package org.matrix.android.sdk.api.session.uia.exceptions -enum class GossipingRequestState { - NONE, - PENDING, - REJECTED, - ACCEPTING, - ACCEPTED, - FAILED_TO_ACCEPTED, - - // USER_REJECTED, - UNABLE_TO_PROCESS, - CANCELLED_BY_REQUESTER, - RE_REQUESTED -} +class UiaCancelledException(message: String? = null) : Exception(message) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/user/UserService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/user/UserService.kt index 063abdb5a0..0c5465e12a 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/user/UserService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/user/UserService.kt @@ -27,14 +27,14 @@ import org.matrix.android.sdk.api.util.Optional interface UserService { /** - * Get a user from a userId + * Get a user from a userId. * @param userId the userId to look for. * @return a user with userId or null */ fun getUser(userId: String): User? /** - * Try to resolve user from known users, or using profile api + * Try to resolve user from known users, or using profile api. */ suspend fun resolveUser(userId: String): User @@ -48,14 +48,14 @@ interface UserService { suspend fun searchUsersDirectory(search: String, limit: Int, excludedUserIds: Set): List /** - * Observe a live user from a userId + * Observe a live user from a userId. * @param userId the userId to look for. * @return a LiveData of user with userId */ fun getUserLive(userId: String): LiveData> /** - * Observe a live list of users sorted alphabetically + * Observe a live list of users sorted alphabetically. * @return a Livedata of users */ fun getUsersLive(): LiveData> @@ -69,19 +69,19 @@ interface UserService { fun getPagedUsersLive(filter: String? = null, excludedUserIds: Set? = null): LiveData> /** - * Get list of ignored users + * Get list of ignored users. */ fun getIgnoredUsersLive(): LiveData> /** - * Ignore users + * Ignore users. * Note: once done, for the change to take effect, you have to request an initial sync. - * This may be improved in the future + * This may be improved in the future. */ suspend fun ignoreUserIds(userIds: List) /** - * Un-ignore some users + * Un-ignore some users. * Note: once done, for the change to take effect, you have to request an initial sync. */ suspend fun unIgnoreUserIds(userIds: List) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/user/model/User.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/user/model/User.kt index 79c86f3f23..f231e88de0 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/user/model/User.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/user/model/User.kt @@ -26,7 +26,7 @@ import org.matrix.android.sdk.api.util.JsonDict data class User( val userId: String, /** - * For usage in UI, consider converting to MatrixItem and call getBestName() + * For usage in UI, consider converting to MatrixItem and call getBestName(). */ val displayName: String? = null, val avatarUrl: String? = null diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/widgets/WidgetPostAPIMediator.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/widgets/WidgetPostAPIMediator.kt index d8fd00d9e1..edb49f4797 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/widgets/WidgetPostAPIMediator.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/widgets/WidgetPostAPIMediator.kt @@ -47,7 +47,7 @@ interface WidgetPostAPIMediator { fun injectAPI() /** - * Send a boolean response + * Send a boolean response. * * @param response the response * @param eventData the modular data @@ -55,7 +55,7 @@ interface WidgetPostAPIMediator { fun sendBoolResponse(response: Boolean, eventData: JsonDict) /** - * Send an integer response + * Send an integer response. * * @param response the response * @param eventData the modular data @@ -63,7 +63,7 @@ interface WidgetPostAPIMediator { fun sendIntegerResponse(response: Int, eventData: JsonDict) /** - * Send an object response + * Send an object response. * * @param klass the class of the response * @param response the response @@ -72,14 +72,14 @@ interface WidgetPostAPIMediator { fun sendObjectResponse(type: Type, response: T?, eventData: JsonDict) /** - * Send success + * Send success. * * @param eventData the modular data */ fun sendSuccess(eventData: JsonDict) /** - * Send an error + * Send an error. * * @param message the error message * @param eventData the modular data @@ -88,7 +88,7 @@ interface WidgetPostAPIMediator { interface Handler { /** - * Triggered when a widget is posting + * Triggered when a widget is posting. */ fun handleWidgetRequest(mediator: WidgetPostAPIMediator, eventData: JsonDict): Boolean } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/widgets/WidgetService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/widgets/WidgetService.kt index 8f35ff0e4a..b06f8f7bc6 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/widgets/WidgetService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/widgets/WidgetService.kt @@ -55,7 +55,7 @@ interface WidgetService { ): List /** - * Return the computed URL of a widget + * Return the computed URL of a widget. */ fun getWidgetComputedUrl(widget: Widget, isLightTheme: Boolean): String? diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/util/Hash.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/util/Hash.kt index 7465eed3ae..22fdd1c610 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/util/Hash.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/util/Hash.kt @@ -20,7 +20,7 @@ import java.security.MessageDigest import java.util.Locale /** - * Compute a Hash of a String, using md5 algorithm + * Compute a Hash of a String, using md5 algorithm. */ fun String.md5() = try { val digest = MessageDigest.getInstance("md5") diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/util/MatrixItem.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/util/MatrixItem.kt index 4f5f4f82d9..8a29d00380 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/util/MatrixItem.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/util/MatrixItem.kt @@ -121,7 +121,7 @@ sealed class MatrixItem( abstract fun updateAvatar(newAvatar: String?): MatrixItem /** - * Return the prefix as defined in the matrix spec (and not extracted from the id) + * Return the prefix as defined in the matrix spec (and not extracted from the id). */ private fun getIdPrefix() = when (this) { is UserItem -> '@' diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/util/MatrixJsonParser.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/util/MatrixJsonParser.kt index 48a41667b2..9927dffa46 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/util/MatrixJsonParser.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/util/MatrixJsonParser.kt @@ -20,7 +20,7 @@ import com.squareup.moshi.Moshi import org.matrix.android.sdk.internal.di.MoshiProvider /** - * Entry point to get a Json parser + * Entry point to get a Json parser. */ object MatrixJsonParser { /** diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/util/TextContent.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/util/TextContent.kt index fe12d7b1cf..2aa548e289 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/util/TextContent.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/util/TextContent.kt @@ -17,7 +17,7 @@ package org.matrix.android.sdk.api.util /** - * Contains a text and eventually a formatted text + * Contains a text and eventually a formatted text. */ data class TextContent( val text: String, diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/AuthAPI.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/AuthAPI.kt index 554e21ce55..46fa63334c 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/AuthAPI.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/AuthAPI.kt @@ -26,6 +26,7 @@ import org.matrix.android.sdk.internal.auth.data.WebClientConfig import org.matrix.android.sdk.internal.auth.login.ResetPasswordMailConfirmed import org.matrix.android.sdk.internal.auth.registration.AddThreePidRegistrationParams import org.matrix.android.sdk.internal.auth.registration.AddThreePidRegistrationResponse +import org.matrix.android.sdk.internal.auth.registration.RegistrationCustomParams import org.matrix.android.sdk.internal.auth.registration.RegistrationParams import org.matrix.android.sdk.internal.auth.registration.SuccessResult import org.matrix.android.sdk.internal.auth.registration.ValidationCodeBody @@ -44,30 +45,38 @@ import retrofit2.http.Url */ internal interface AuthAPI { /** - * Get a Web client config file, using the name including the domain + * Get a Web client config file, using the name including the domain. */ @GET("config.{domain}.json") suspend fun getWebClientConfigDomain(@Path("domain") domain: String): WebClientConfig /** - * Get a Web client default config file + * Get a Web client default config file. */ @GET("config.json") suspend fun getWebClientConfig(): WebClientConfig /** - * Get the version information of the homeserver + * Get the version information of the homeserver. */ @GET(NetworkConstants.URI_API_PREFIX_PATH_ + "versions") suspend fun versions(): Versions /** - * Register to the homeserver, or get error 401 with a RegistrationFlowResponse object if registration is incomplete - * Ref: https://matrix.org/docs/spec/client_server/latest#account-registration-and-management + * Register to the homeserver, or get error 401 with a RegistrationFlowResponse object if registration is incomplete. + * Ref: https://matrix.org/docs/spec/client_server/latest#account-registration-and-management. */ @POST(NetworkConstants.URI_API_PREFIX_PATH_R0 + "register") suspend fun register(@Body registrationParams: RegistrationParams): Credentials + /** + * Register to the homeserver, or get error 401 with a RegistrationFlowResponse object if registration is incomplete + * method to perform other custom stages + * Ref: https://matrix.org/docs/spec/client_server/latest#account-registration-and-management + */ + @POST(NetworkConstants.URI_API_PREFIX_PATH_R0 + "register") + suspend fun registerCustom(@Body registrationCustomParams: RegistrationCustomParams): Credentials + /** * Checks to see if a username is available, and valid, for the server. */ @@ -93,14 +102,14 @@ internal interface AuthAPI { @Body params: AddThreePidRegistrationParams): AddThreePidRegistrationResponse /** - * Validate 3pid + * Validate 3pid. */ @POST suspend fun validate3Pid(@Url url: String, @Body params: ValidationCodeBody): SuccessResult /** - * Get the supported login flow + * Get the supported login flow. * Ref: https://matrix.org/docs/spec/client_server/latest#get-matrix-client-r0-login */ @GET(NetworkConstants.URI_API_PREFIX_PATH_R0 + "login") diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/Constants.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/Constants.kt index 3742a429d2..97f8a9d512 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/Constants.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/Constants.kt @@ -23,13 +23,13 @@ package org.matrix.android.sdk.internal.auth internal const val LOGIN_FALLBACK_PATH = "/_matrix/static/client/login/" /** - * Path to use when the client does not supported any or all registration flows - * Not documented + * Path to use when the client does not supported any or all registration flows. + * Not documented. */ internal const val REGISTER_FALLBACK_PATH = "/_matrix/static/client/register/" /** - * Path to use when the client want to connect using SSO + * Path to use when the client want to connect using SSO. * Ref: https://matrix.org/docs/spec/client_server/latest#sso-client-login */ internal const val SSO_REDIRECT_PATH = "/_matrix/client/r0/login/sso/redirect" diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/DefaultAuthenticationService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/DefaultAuthenticationService.kt index 8784d85c10..f1cfe3fee5 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/DefaultAuthenticationService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/DefaultAuthenticationService.kt @@ -379,24 +379,33 @@ internal class DefaultAuthenticationService @Inject constructor( throw MatrixIdFailure.InvalidMatrixId } - return getWellknownTask.execute(GetWellknownTask.Params( - domain = matrixId.getDomain(), - homeServerConnectionConfig = homeServerConnectionConfig) + return getWellknownTask.execute( + GetWellknownTask.Params( + domain = matrixId.getDomain(), + homeServerConnectionConfig = homeServerConnectionConfig.orWellKnownDefaults() + ) ) } + private fun HomeServerConnectionConfig?.orWellKnownDefaults() = this ?: HomeServerConnectionConfig.Builder() + // server uri is ignored when doing a wellknown lookup as we use the matrix id domain instead + .withHomeServerUri("https://dummy.org") + .build() + override suspend fun directAuthentication(homeServerConnectionConfig: HomeServerConnectionConfig, matrixId: String, password: String, initialDeviceName: String, deviceId: String?): Session { - return directLoginTask.execute(DirectLoginTask.Params( - homeServerConnectionConfig = homeServerConnectionConfig, - userId = matrixId, - password = password, - deviceName = initialDeviceName, - deviceId = deviceId - )) + return directLoginTask.execute( + DirectLoginTask.Params( + homeServerConnectionConfig = homeServerConnectionConfig, + userId = matrixId, + password = password, + deviceName = initialDeviceName, + deviceId = deviceId + ) + ) } private fun buildAuthAPI(homeServerConnectionConfig: HomeServerConnectionConfig): AuthAPI { diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/PendingSessionStore.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/PendingSessionStore.kt index 06954fa5c2..7483e2c7d9 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/PendingSessionStore.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/PendingSessionStore.kt @@ -19,7 +19,7 @@ package org.matrix.android.sdk.internal.auth import org.matrix.android.sdk.internal.auth.db.PendingSessionData /** - * Store for elements when doing login or registration + * Store for elements when doing login or registration. */ internal interface PendingSessionStore { diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/SessionCreator.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/SessionCreator.kt index cc00c963ea..ba01146a4a 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/SessionCreator.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/SessionCreator.kt @@ -39,7 +39,7 @@ internal class DefaultSessionCreator @Inject constructor( /** * Credentials can affect the homeServerConnectionConfig, override homeserver url and/or - * identity server url if provided in the credentials + * identity server url if provided in the credentials. */ override suspend fun createSession(credentials: Credentials, homeServerConnectionConfig: HomeServerConnectionConfig): Session { // We can cleanup the pending session params diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/data/LoginFlowResponse.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/data/LoginFlowResponse.kt index c718fae390..df10e110d1 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/data/LoginFlowResponse.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/data/LoginFlowResponse.kt @@ -23,7 +23,7 @@ import org.matrix.android.sdk.api.auth.data.SsoIdentityProvider @JsonClass(generateAdapter = true) internal data class LoginFlowResponse( /** - * The homeserver's supported login types + * The homeserver's supported login types. */ @Json(name = "flows") val flows: List? @@ -39,7 +39,7 @@ internal data class LoginFlow( /** * Augments m.login.sso flow discovery definition to include metadata on the supported IDPs - * the client can show a button for each of the supported providers + * the client can show a button for each of the supported providers. * See MSC #2858 */ @Json(name = "identity_providers") diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/data/WebClientConfig.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/data/WebClientConfig.kt index 65c3dc64a6..a993124a7e 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/data/WebClientConfig.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/data/WebClientConfig.kt @@ -22,7 +22,7 @@ import com.squareup.moshi.JsonClass @JsonClass(generateAdapter = true) internal data class WebClientConfig( /** - * This is now deprecated, but still used first, rather than value from "default_server_config" + * This is now deprecated, but still used first, rather than value from "default_server_config". */ @Json(name = "default_hs_url") val defaultHomeServerUrl: String?, diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/db/AuthRealmMigration.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/db/AuthRealmMigration.kt index 59b6471a05..88c6d04ee6 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/db/AuthRealmMigration.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/db/AuthRealmMigration.kt @@ -27,8 +27,8 @@ import javax.inject.Inject internal class AuthRealmMigration @Inject constructor() : RealmMigration { /** - * Forces all AuthRealmMigration instances to be equal - * Avoids Realm throwing when multiple instances of the migration are set + * Forces all AuthRealmMigration instances to be equal. + * Avoids Realm throwing when multiple instances of the migration are set. */ override fun equals(other: Any?) = other is AuthRealmMigration override fun hashCode() = 4000 diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/db/AuthRealmModule.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/db/AuthRealmModule.kt index a92384b4ed..dbf1977602 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/db/AuthRealmModule.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/db/AuthRealmModule.kt @@ -19,11 +19,13 @@ package org.matrix.android.sdk.internal.auth.db import io.realm.annotations.RealmModule /** - * Realm module for authentication classes + * Realm module for authentication classes. */ -@RealmModule(library = true, +@RealmModule( + library = true, classes = [ SessionParamsEntity::class, PendingSessionEntity::class - ]) + ] +) internal class AuthRealmModule diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/db/PendingSessionData.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/db/PendingSessionData.kt index 6e13e947f4..835070d4b7 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/db/PendingSessionData.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/db/PendingSessionData.kt @@ -22,7 +22,7 @@ import org.matrix.android.sdk.internal.auth.registration.ThreePidData import java.util.UUID /** - * This class holds all pending data when creating a session, either by login or by register + * This class holds all pending data when creating a session, either by login or by register. */ internal data class PendingSessionData( val homeServerConnectionConfig: HomeServerConnectionConfig, diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/db/PendingSessionMapper.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/db/PendingSessionMapper.kt index 8e4043c11b..1296ea7cc4 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/db/PendingSessionMapper.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/db/PendingSessionMapper.kt @@ -44,7 +44,8 @@ internal class PendingSessionMapper @Inject constructor(moshi: Moshi) { resetPasswordData = resetPasswordData, currentSession = entity.currentSession, isRegistrationStarted = entity.isRegistrationStarted, - currentThreePidData = threePidData) + currentThreePidData = threePidData + ) } fun map(sessionData: PendingSessionData?): PendingSessionEntity? { diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/db/SessionParamsMapper.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/db/SessionParamsMapper.kt index 147c0e8be0..86929b1afe 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/db/SessionParamsMapper.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/db/SessionParamsMapper.kt @@ -54,6 +54,7 @@ internal class SessionParamsMapper @Inject constructor(moshi: Moshi) { sessionParams.userId, credentialsJson, homeServerConnectionConfigJson, - sessionParams.isTokenValid) + sessionParams.isTokenValid + ) } } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/login/ResetPasswordData.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/login/ResetPasswordData.kt index 06fcacd514..a65ec38d6d 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/login/ResetPasswordData.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/login/ResetPasswordData.kt @@ -20,7 +20,7 @@ import com.squareup.moshi.JsonClass import org.matrix.android.sdk.internal.auth.registration.AddThreePidRegistrationResponse /** - * Container to store the data when a reset password is in the email validation step + * Container to store the data when a reset password is in the email validation step. */ @JsonClass(generateAdapter = true) internal data class ResetPasswordData( diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/registration/AddThreePidRegistrationParams.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/registration/AddThreePidRegistrationParams.kt index f8d17b406a..2f05864d3b 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/registration/AddThreePidRegistrationParams.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/registration/AddThreePidRegistrationParams.kt @@ -21,7 +21,7 @@ import com.squareup.moshi.JsonClass import org.matrix.android.sdk.api.auth.registration.RegisterThreePid /** - * Add a three Pid during authentication + * Add a three Pid during authentication. */ @JsonClass(generateAdapter = true) internal data class AddThreePidRegistrationParams( diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/registration/AuthParams.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/registration/AuthParams.kt index cb17207741..6337a6e5c5 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/registration/AuthParams.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/registration/AuthParams.kt @@ -21,7 +21,7 @@ import com.squareup.moshi.JsonClass import org.matrix.android.sdk.api.auth.data.LoginFlowTypes /** - * Open class, parent to all possible authentication parameters + * Open class, parent to all possible authentication parameters. */ @JsonClass(generateAdapter = true) internal data class AuthParams( @@ -29,19 +29,19 @@ internal data class AuthParams( val type: String, /** - * Note: session can be null for reset password request + * Note: session can be null for reset password request. */ @Json(name = "session") val session: String?, /** - * parameter for "m.login.recaptcha" type + * parameter for "m.login.recaptcha" type. */ @Json(name = "response") val captchaResponse: String? = null, /** - * parameter for "m.login.email.identity" type + * parameter for "m.login.email.identity" type. */ @Json(name = "threepid_creds") val threePidCredentials: ThreePidCredentials? = null diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/registration/DefaultRegistrationWizard.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/registration/DefaultRegistrationWizard.kt index 4a156e74cd..8f00f3440c 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/registration/DefaultRegistrationWizard.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/registration/DefaultRegistrationWizard.kt @@ -17,6 +17,7 @@ package org.matrix.android.sdk.internal.auth.registration import kotlinx.coroutines.delay +import org.matrix.android.sdk.api.auth.data.Credentials import org.matrix.android.sdk.api.auth.data.LoginFlowTypes import org.matrix.android.sdk.api.auth.registration.RegisterThreePid import org.matrix.android.sdk.api.auth.registration.RegistrationAvailability @@ -25,13 +26,14 @@ import org.matrix.android.sdk.api.auth.registration.RegistrationWizard import org.matrix.android.sdk.api.auth.registration.toFlowResult import org.matrix.android.sdk.api.failure.Failure import org.matrix.android.sdk.api.failure.Failure.RegistrationFlowError +import org.matrix.android.sdk.api.util.JsonDict import org.matrix.android.sdk.internal.auth.AuthAPI import org.matrix.android.sdk.internal.auth.PendingSessionStore import org.matrix.android.sdk.internal.auth.SessionCreator import org.matrix.android.sdk.internal.auth.db.PendingSessionData /** - * This class execute the registration request and is responsible to keep the session of interactive authentication + * This class execute the registration request and is responsible to keep the session of interactive authentication. */ internal class DefaultRegistrationWizard( authAPI: AuthAPI, @@ -45,6 +47,7 @@ internal class DefaultRegistrationWizard( private val registerAvailableTask: RegisterAvailableTask = DefaultRegisterAvailableTask(authAPI) private val registerAddThreePidTask: RegisterAddThreePidTask = DefaultRegisterAddThreePidTask(authAPI) private val validateCodeTask: ValidateCodeTask = DefaultValidateCodeTask(authAPI) + private val registerCustomTask: RegisterCustomTask = DefaultRegisterCustomTask(authAPI) override val currentThreePid: String? get() { @@ -117,21 +120,25 @@ internal class DefaultRegistrationWizard( RegisterAddThreePidTask.Params( threePid, pendingSessionData.clientSecret, - pendingSessionData.sendAttempt)) + pendingSessionData.sendAttempt + ) + ) pendingSessionData = pendingSessionData.copy(sendAttempt = pendingSessionData.sendAttempt + 1) .also { pendingSessionStore.savePendingSessionData(it) } val params = RegistrationParams( auth = if (threePid is RegisterThreePid.Email) { - AuthParams.createForEmailIdentity(safeSession, + AuthParams.createForEmailIdentity( + safeSession, ThreePidCredentials( clientSecret = pendingSessionData.clientSecret, sid = response.sid ) ) } else { - AuthParams.createForMsisdnIdentity(safeSession, + AuthParams.createForMsisdnIdentity( + safeSession, ThreePidCredentials( clientSecret = pendingSessionData.clientSecret, sid = response.sid @@ -187,22 +194,51 @@ internal class DefaultRegistrationWizard( return performRegistrationRequest(params) } - private suspend fun performRegistrationRequest(registrationParams: RegistrationParams, - delayMillis: Long = 0): RegistrationResult { + override suspend fun registrationCustom( + authParams: JsonDict + ): RegistrationResult { + val safeSession = pendingSessionData.currentSession + ?: throw IllegalStateException("developer error, call createAccount() method first") + + val mutableParams = authParams.toMutableMap() + mutableParams["session"] = safeSession + + val params = RegistrationCustomParams(auth = mutableParams) + return performRegistrationOtherRequest(params) + } + + private suspend fun performRegistrationRequest( + registrationParams: RegistrationParams, + delayMillis: Long = 0 + ): RegistrationResult { delay(delayMillis) + return register { registerTask.execute(RegisterTask.Params(registrationParams)) } + } + + private suspend fun performRegistrationOtherRequest( + registrationCustomParams: RegistrationCustomParams + ): RegistrationResult { + return register { registerCustomTask.execute(RegisterCustomTask.Params(registrationCustomParams)) } + } + + private suspend fun register( + execute: suspend () -> Credentials + ): RegistrationResult { val credentials = try { - registerTask.execute(RegisterTask.Params(registrationParams)) + execute.invoke() } catch (exception: Throwable) { if (exception is RegistrationFlowError) { - pendingSessionData = pendingSessionData.copy(currentSession = exception.registrationFlowResponse.session) - .also { pendingSessionStore.savePendingSessionData(it) } + pendingSessionData = + pendingSessionData.copy(currentSession = exception.registrationFlowResponse.session) + .also { pendingSessionStore.savePendingSessionData(it) } return RegistrationResult.FlowResponse(exception.registrationFlowResponse.toFlowResult()) } else { throw exception } } - val session = sessionCreator.createSession(credentials, pendingSessionData.homeServerConnectionConfig) + val session = + sessionCreator.createSession(credentials, pendingSessionData.homeServerConnectionConfig) return RegistrationResult.Success(session) } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/registration/RegisterCustomTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/registration/RegisterCustomTask.kt new file mode 100644 index 0000000000..60af708c38 --- /dev/null +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/registration/RegisterCustomTask.kt @@ -0,0 +1,47 @@ +/* + * Copyright 2021 The Matrix.org Foundation C.I.C. + * + * 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 org.matrix.android.sdk.internal.auth.registration + +import org.matrix.android.sdk.api.auth.data.Credentials +import org.matrix.android.sdk.api.failure.Failure +import org.matrix.android.sdk.api.failure.toRegistrationFlowResponse +import org.matrix.android.sdk.internal.auth.AuthAPI +import org.matrix.android.sdk.internal.network.executeRequest +import org.matrix.android.sdk.internal.task.Task + +internal interface RegisterCustomTask : Task { + data class Params( + val registrationCustomParams: RegistrationCustomParams + ) +} + +internal class DefaultRegisterCustomTask( + private val authAPI: AuthAPI +) : RegisterCustomTask { + + override suspend fun execute(params: RegisterCustomTask.Params): Credentials { + try { + return executeRequest(null) { + authAPI.registerCustom(params.registrationCustomParams) + } + } catch (throwable: Throwable) { + throw throwable.toRegistrationFlowResponse() + ?.let { Failure.RegistrationFlowError(it) } + ?: throwable + } + } +} diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/livelocation/BeaconInfo.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/registration/RegistrationCustomParams.kt similarity index 56% rename from matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/livelocation/BeaconInfo.kt rename to matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/registration/RegistrationCustomParams.kt index 873edc0f1f..45adac6c26 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/livelocation/BeaconInfo.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/registration/RegistrationCustomParams.kt @@ -1,5 +1,5 @@ /* - * Copyright 2022 The Matrix.org Foundation C.I.C. + * Copyright 2021 The Matrix.org Foundation C.I.C. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,20 +14,18 @@ * limitations under the License. */ -package org.matrix.android.sdk.api.session.room.model.livelocation +package org.matrix.android.sdk.internal.auth.registration import com.squareup.moshi.Json import com.squareup.moshi.JsonClass +import org.matrix.android.sdk.api.util.JsonDict +/** + * Class to pass parameters to the custom registration types for /register. + */ @JsonClass(generateAdapter = true) -data class BeaconInfo( - @Json(name = "description") val description: String? = null, - /** - * Beacon should be considered as inactive after this timeout as milliseconds. - */ - @Json(name = "timeout") val timeout: Long? = null, - /** - * Should be set true to start sharing beacon. - */ - @Json(name = "live") val isLive: Boolean? = null +internal data class RegistrationCustomParams( + // authentication parameters + @Json(name = "auth") + val auth: JsonDict? = null, ) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/registration/ThreePidData.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/registration/ThreePidData.kt index 43167062d5..c8f71af306 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/registration/ThreePidData.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/registration/ThreePidData.kt @@ -20,7 +20,7 @@ import com.squareup.moshi.JsonClass import org.matrix.android.sdk.api.auth.registration.RegisterThreePid /** - * Container to store the data when a three pid is in validation step + * Container to store the data when a three pid is in validation step. */ @JsonClass(generateAdapter = true) internal data class ThreePidData( diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/registration/UIAExt.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/registration/UIAExt.kt index da0866a5fd..9b135c347d 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/registration/UIAExt.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/registration/UIAExt.kt @@ -20,24 +20,27 @@ import org.matrix.android.sdk.api.auth.UIABaseAuth import org.matrix.android.sdk.api.auth.UserInteractiveAuthInterceptor import org.matrix.android.sdk.api.failure.Failure import org.matrix.android.sdk.api.failure.toRegistrationFlowResponse +import org.matrix.android.sdk.api.session.uia.UiaResult +import org.matrix.android.sdk.api.session.uia.exceptions.UiaCancelledException import timber.log.Timber import kotlin.coroutines.suspendCoroutine /** - * Handle a UIA challenge + * Handle a UIA challenge. * * @param failure the failure to handle * @param interceptor see doc in [UserInteractiveAuthInterceptor] * @param retryBlock called at the end of the process, in this block generally retry executing the task, with * provided authUpdate - * @return true if UIA is handled without error + * @return UiaResult if UIA handled, failed or cancelled + * */ internal suspend fun handleUIA(failure: Throwable, interceptor: UserInteractiveAuthInterceptor, - retryBlock: suspend (UIABaseAuth) -> Unit): Boolean { + retryBlock: suspend (UIABaseAuth) -> Unit): UiaResult { Timber.d("## UIA: check error ${failure.message}") val flowResponse = failure.toRegistrationFlowResponse() - ?: return false.also { + ?: return UiaResult.FAILURE.also { Timber.d("## UIA: not a UIA error") } @@ -50,14 +53,19 @@ internal suspend fun handleUIA(failure: Throwable, interceptor.performStage(flowResponse, (failure as? Failure.ServerError)?.error?.code, continuation) } } catch (failure2: Throwable) { - Timber.w(failure2, "## UIA: failed to participate") - return false + return if (failure2 is UiaCancelledException) { + Timber.w(failure2, "## UIA: cancelled") + UiaResult.CANCELLED + } else { + Timber.w(failure2, "## UIA: failed to participate") + UiaResult.FAILURE + } } Timber.d("## UIA: updated auth") return try { retryBlock(authUpdate) - true + UiaResult.SUCCESS } catch (failure3: Throwable) { handleUIA(failure3, interceptor, retryBlock) } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/registration/ValidationCodeBody.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/registration/ValidationCodeBody.kt index ae71ae3a08..72b3dc6103 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/registration/ValidationCodeBody.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/registration/ValidationCodeBody.kt @@ -20,7 +20,7 @@ import com.squareup.moshi.Json import com.squareup.moshi.JsonClass /** - * This object is used to send a code received by SMS to validate Msisdn ownership + * This object is used to send a code received by SMS to validate Msisdn ownership. */ @JsonClass(generateAdapter = true) internal data class ValidationCodeBody( diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/version/Versions.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/version/Versions.kt index d07d5ecd64..cee4b12138 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/version/Versions.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/version/Versions.kt @@ -20,7 +20,7 @@ import com.squareup.moshi.Json import com.squareup.moshi.JsonClass /** - * Model for https://matrix.org/docs/spec/client_server/latest#get-matrix-client-versions + * Model for https://matrix.org/docs/spec/client_server/latest#get-matrix-client-versions. * * Ex: *
@@ -55,14 +55,14 @@ private const val FEATURE_THREADS_MSC3440 = "org.matrix.msc3440"
 private const val FEATURE_THREADS_MSC3440_STABLE = "org.matrix.msc3440.stable"
 
 /**
- * Return true if the SDK supports this homeserver version
+ * Return true if the SDK supports this homeserver version.
  */
 internal fun Versions.isSupportedBySdk(): Boolean {
     return supportLazyLoadMembers()
 }
 
 /**
- * Return true if the SDK supports this homeserver version for login and registration
+ * Return true if the SDK supports this homeserver version for login and registration.
  */
 internal fun Versions.isLoginAndRegistrationSupportedBySdk(): Boolean {
     return !doesServerRequireIdentityServerParam() &&
@@ -71,15 +71,15 @@ internal fun Versions.isLoginAndRegistrationSupportedBySdk(): Boolean {
 }
 
 /**
- * Indicate if the homeserver support MSC3440 for threads
+ * Indicate if the homeserver support MSC3440 for threads.
  */
 internal fun Versions.doesServerSupportThreads(): Boolean {
-    return getMaxVersion() >= HomeServerVersion.v1_3_0 ||
-            unstableFeatures?.get(FEATURE_THREADS_MSC3440_STABLE) ?: false
+    // TODO Check for v1.3 or whichever spec version formally specifies MSC3440.
+    return unstableFeatures?.get(FEATURE_THREADS_MSC3440_STABLE) ?: false
 }
 
 /**
- * Return true if the server support the lazy loading of room members
+ * Return true if the server support the lazy loading of room members.
  *
  * @return true if the server support the lazy loading of room members
  */
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/CancelGossipRequestWorker.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/CancelGossipRequestWorker.kt
deleted file mode 100644
index 98950374ed..0000000000
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/CancelGossipRequestWorker.kt
+++ /dev/null
@@ -1,121 +0,0 @@
-/*
- * Copyright 2020 The Matrix.org Foundation C.I.C.
- *
- * 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 org.matrix.android.sdk.internal.crypto
-
-import android.content.Context
-import androidx.work.WorkerParameters
-import com.squareup.moshi.JsonClass
-import org.matrix.android.sdk.api.auth.data.Credentials
-import org.matrix.android.sdk.api.failure.shouldBeRetried
-import org.matrix.android.sdk.api.session.crypto.model.MXUsersDevicesMap
-import org.matrix.android.sdk.api.session.crypto.model.OutgoingGossipingRequestState
-import org.matrix.android.sdk.api.session.events.model.Event
-import org.matrix.android.sdk.api.session.events.model.EventType
-import org.matrix.android.sdk.api.session.events.model.toContent
-import org.matrix.android.sdk.internal.SessionManager
-import org.matrix.android.sdk.internal.crypto.model.rest.ShareRequestCancellation
-import org.matrix.android.sdk.internal.crypto.store.IMXCryptoStore
-import org.matrix.android.sdk.internal.crypto.tasks.SendToDeviceTask
-import org.matrix.android.sdk.internal.crypto.tasks.createUniqueTxnId
-import org.matrix.android.sdk.internal.session.SessionComponent
-import org.matrix.android.sdk.internal.worker.SessionSafeCoroutineWorker
-import org.matrix.android.sdk.internal.worker.SessionWorkerParams
-import javax.inject.Inject
-
-internal class CancelGossipRequestWorker(context: Context, params: WorkerParameters, sessionManager: SessionManager) :
-    SessionSafeCoroutineWorker(context, params, sessionManager, Params::class.java) {
-
-    @JsonClass(generateAdapter = true)
-    internal data class Params(
-            override val sessionId: String,
-            val requestId: String,
-            val recipients: Map>,
-            // The txnId for the sendToDevice request. Nullable for compatibility reasons, but MUST always be provided
-            // to use the same value if this worker is retried.
-            val txnId: String? = null,
-            override val lastFailureMessage: String? = null
-    ) : SessionWorkerParams {
-        companion object {
-            fun fromRequest(sessionId: String, request: OutgoingGossipingRequest): Params {
-                return Params(
-                        sessionId = sessionId,
-                        requestId = request.requestId,
-                        recipients = request.recipients,
-                        txnId = createUniqueTxnId(),
-                        lastFailureMessage = null
-                )
-            }
-        }
-    }
-
-    @Inject lateinit var sendToDeviceTask: SendToDeviceTask
-    @Inject lateinit var cryptoStore: IMXCryptoStore
-    @Inject lateinit var credentials: Credentials
-
-    override fun injectWith(injector: SessionComponent) {
-        injector.inject(this)
-    }
-
-    override suspend fun doSafeWork(params: Params): Result {
-        // params.txnId should be provided in all cases now. But Params can be deserialized by
-        // the WorkManager from data serialized in a previous version of the application, so without the txnId field.
-        // So if not present, we create a txnId
-        val txnId = params.txnId ?: createUniqueTxnId()
-        val contentMap = MXUsersDevicesMap()
-        val toDeviceContent = ShareRequestCancellation(
-                requestingDeviceId = credentials.deviceId,
-                requestId = params.requestId
-        )
-        cryptoStore.saveGossipingEvent(Event(
-                type = EventType.ROOM_KEY_REQUEST,
-                content = toDeviceContent.toContent(),
-                senderId = credentials.userId
-        ).also {
-            it.ageLocalTs = System.currentTimeMillis()
-        })
-
-        params.recipients.forEach { userToDeviceMap ->
-            userToDeviceMap.value.forEach { deviceId ->
-                contentMap.setObject(userToDeviceMap.key, deviceId, toDeviceContent)
-            }
-        }
-
-        try {
-            cryptoStore.updateOutgoingGossipingRequestState(params.requestId, OutgoingGossipingRequestState.CANCELLING)
-            sendToDeviceTask.execute(
-                    SendToDeviceTask.Params(
-                            eventType = EventType.ROOM_KEY_REQUEST,
-                            contentMap = contentMap,
-                            transactionId = txnId
-                    )
-            )
-            cryptoStore.updateOutgoingGossipingRequestState(params.requestId, OutgoingGossipingRequestState.CANCELLED)
-            return Result.success()
-        } catch (throwable: Throwable) {
-            return if (throwable.shouldBeRetried()) {
-                Result.retry()
-            } else {
-                cryptoStore.updateOutgoingGossipingRequestState(params.requestId, OutgoingGossipingRequestState.FAILED_TO_CANCEL)
-                buildErrorResult(params, throwable.localizedMessage ?: "error")
-            }
-        }
-    }
-
-    override fun buildErrorParams(params: Params, message: String): Params {
-        return params.copy(lastFailureMessage = params.lastFailureMessage ?: message)
-    }
-}
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/CryptoSessionInfoProvider.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/CryptoSessionInfoProvider.kt
index 2a58d731e5..b3e9eab988 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/CryptoSessionInfoProvider.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/CryptoSessionInfoProvider.kt
@@ -28,7 +28,7 @@ import javax.inject.Inject
 
 /**
  * The crypto module needs some information regarding rooms that are stored
- * in the session DB, this class encapsulate this functionality
+ * in the session DB, this class encapsulate this functionality.
  */
 internal class CryptoSessionInfoProvider @Inject constructor(
         @SessionDatabase private val monarchy: Monarchy
@@ -48,7 +48,7 @@ internal class CryptoSessionInfoProvider @Inject constructor(
     /**
      * @param allActive if true return joined as well as invited, if false, only joined
      */
-     fun getRoomUserIds(roomId: String, allActive: Boolean): List {
+    fun getRoomUserIds(roomId: String, allActive: Boolean): List {
         var userIds: List = emptyList()
         monarchy.doWithRealm { realm ->
             userIds = if (allActive) {
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/DefaultCryptoService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/DefaultCryptoService.kt
index 6a57d94677..11fa93dbe0 100755
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/DefaultCryptoService.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/DefaultCryptoService.kt
@@ -42,12 +42,14 @@ import org.matrix.android.sdk.api.logger.LoggerTag
 import org.matrix.android.sdk.api.session.crypto.CryptoService
 import org.matrix.android.sdk.api.session.crypto.MXCryptoError
 import org.matrix.android.sdk.api.session.crypto.NewSessionListener
+import org.matrix.android.sdk.api.session.crypto.OutgoingKeyRequest
 import org.matrix.android.sdk.api.session.crypto.crosssigning.DeviceTrustLevel
 import org.matrix.android.sdk.api.session.crypto.crosssigning.KEYBACKUP_SECRET_SSSS_NAME
 import org.matrix.android.sdk.api.session.crypto.crosssigning.MASTER_KEY_SSSS_NAME
 import org.matrix.android.sdk.api.session.crypto.crosssigning.SELF_SIGNING_KEY_SSSS_NAME
 import org.matrix.android.sdk.api.session.crypto.crosssigning.USER_SIGNING_KEY_SSSS_NAME
 import org.matrix.android.sdk.api.session.crypto.keyshare.GossipingRequestListener
+import org.matrix.android.sdk.api.session.crypto.model.AuditTrail
 import org.matrix.android.sdk.api.session.crypto.model.CryptoDeviceInfo
 import org.matrix.android.sdk.api.session.crypto.model.DeviceInfo
 import org.matrix.android.sdk.api.session.crypto.model.DevicesListResponse
@@ -57,15 +59,13 @@ import org.matrix.android.sdk.api.session.crypto.model.MXDeviceInfo
 import org.matrix.android.sdk.api.session.crypto.model.MXEncryptEventContentResult
 import org.matrix.android.sdk.api.session.crypto.model.MXEventDecryptionResult
 import org.matrix.android.sdk.api.session.crypto.model.MXUsersDevicesMap
-import org.matrix.android.sdk.api.session.crypto.model.OutgoingRoomKeyRequest
-import org.matrix.android.sdk.api.session.crypto.model.RoomKeyRequestBody
+import org.matrix.android.sdk.api.session.crypto.model.RoomKeyShareRequest
+import org.matrix.android.sdk.api.session.crypto.model.TrailType
 import org.matrix.android.sdk.api.session.events.model.Content
 import org.matrix.android.sdk.api.session.events.model.Event
 import org.matrix.android.sdk.api.session.events.model.EventType
-import org.matrix.android.sdk.api.session.events.model.content.EncryptedEventContent
 import org.matrix.android.sdk.api.session.events.model.content.RoomKeyContent
 import org.matrix.android.sdk.api.session.events.model.content.RoomKeyWithHeldContent
-import org.matrix.android.sdk.api.session.events.model.content.SecretSendEventContent
 import org.matrix.android.sdk.api.session.events.model.toModel
 import org.matrix.android.sdk.api.session.room.model.Membership
 import org.matrix.android.sdk.api.session.room.model.RoomHistoryVisibility
@@ -76,7 +76,6 @@ import org.matrix.android.sdk.internal.crypto.actions.MegolmSessionDataImporter
 import org.matrix.android.sdk.internal.crypto.actions.SetDeviceVerificationAction
 import org.matrix.android.sdk.internal.crypto.algorithms.IMXEncrypting
 import org.matrix.android.sdk.internal.crypto.algorithms.IMXGroupEncryption
-import org.matrix.android.sdk.internal.crypto.algorithms.IMXWithHeldExtension
 import org.matrix.android.sdk.internal.crypto.algorithms.megolm.MXMegolmEncryptionFactory
 import org.matrix.android.sdk.internal.crypto.algorithms.olm.MXOlmEncryptionFactory
 import org.matrix.android.sdk.internal.crypto.crosssigning.DefaultCrossSigningService
@@ -91,6 +90,7 @@ import org.matrix.android.sdk.internal.crypto.tasks.GetDevicesTask
 import org.matrix.android.sdk.internal.crypto.tasks.SetDeviceNameTask
 import org.matrix.android.sdk.internal.crypto.tasks.UploadKeysTask
 import org.matrix.android.sdk.internal.crypto.verification.DefaultVerificationService
+import org.matrix.android.sdk.internal.crypto.verification.VerificationMessageProcessor
 import org.matrix.android.sdk.internal.di.DeviceId
 import org.matrix.android.sdk.internal.di.MoshiProvider
 import org.matrix.android.sdk.internal.di.UserId
@@ -103,6 +103,7 @@ import org.matrix.android.sdk.internal.task.TaskThread
 import org.matrix.android.sdk.internal.task.configureWith
 import org.matrix.android.sdk.internal.task.launchToCallback
 import org.matrix.android.sdk.internal.util.JsonCanonicalizer
+import org.matrix.android.sdk.internal.util.time.Clock
 import org.matrix.olm.OlmManager
 import timber.log.Timber
 import java.util.concurrent.atomic.AtomicBoolean
@@ -130,6 +131,7 @@ internal class DefaultCryptoService @Inject constructor(
         private val userId: String,
         @DeviceId
         private val deviceId: String?,
+        private val clock: Clock,
         private val myDeviceInfoHolder: Lazy,
         // the crypto store
         private val cryptoStore: IMXCryptoStore,
@@ -154,9 +156,10 @@ internal class DefaultCryptoService @Inject constructor(
 
         private val crossSigningService: DefaultCrossSigningService,
         //
-        private val incomingGossipingRequestManager: IncomingGossipingRequestManager,
+        private val incomingKeyRequestManager: IncomingKeyRequestManager,
+        private val secretShareManager: SecretShareManager,
         //
-        private val outgoingGossipingRequestManager: OutgoingGossipingRequestManager,
+        private val outgoingKeyRequestManager: OutgoingKeyRequestManager,
         // Actions
         private val setDeviceVerificationAction: SetDeviceVerificationAction,
         private val megolmSessionDataImporter: MegolmSessionDataImporter,
@@ -176,6 +179,7 @@ internal class DefaultCryptoService @Inject constructor(
         private val taskExecutor: TaskExecutor,
         private val cryptoCoroutineScope: CoroutineScope,
         private val eventDecryptor: EventDecryptor,
+        private val verificationMessageProcessor: VerificationMessageProcessor,
         private val liveEventManager: Lazy
 ) : CryptoService {
 
@@ -190,7 +194,7 @@ internal class DefaultCryptoService @Inject constructor(
         }
     }
 
-    fun onLiveEvent(roomId: String, event: Event) {
+    fun onLiveEvent(roomId: String, event: Event, isInitialSync: Boolean) {
         // handle state events
         if (event.isStateEvent()) {
             when (event.type) {
@@ -199,9 +203,18 @@ internal class DefaultCryptoService @Inject constructor(
                 EventType.STATE_ROOM_HISTORY_VISIBILITY -> onRoomHistoryVisibilityEvent(roomId, event)
             }
         }
+
+        // handle verification
+        if (!isInitialSync) {
+            if (event.type != null && verificationMessageProcessor.shouldProcess(event.type)) {
+                cryptoCoroutineScope.launch(coroutineDispatchers.dmVerif) {
+                    verificationMessageProcessor.process(event)
+                }
+            }
+        }
     }
 
-    val gossipingBuffer = mutableListOf()
+//    val gossipingBuffer = mutableListOf()
 
     override fun setDeviceName(deviceId: String, deviceName: String, callback: MatrixCallback) {
         setDeviceNameTask
@@ -280,7 +293,7 @@ internal class DefaultCryptoService @Inject constructor(
     }
 
     /**
-     * Provides the tracking status
+     * Provides the tracking status.
      *
      * @param userId the user id
      * @return the tracking status
@@ -290,7 +303,7 @@ internal class DefaultCryptoService @Inject constructor(
     }
 
     /**
-     * Tell if the MXCrypto is started
+     * Tell if the MXCrypto is started.
      *
      * @return true if the crypto is started
      */
@@ -377,35 +390,17 @@ internal class DefaultCryptoService @Inject constructor(
         // Open the store
         cryptoStore.open()
 
-        runCatching {
-//            if (isInitialSync) {
-//                // refresh the devices list for each known room members
-//                deviceListManager.invalidateAllDeviceLists()
-//                deviceListManager.refreshOutdatedDeviceLists()
-//            } else {
-
-            // Why would we do that? it will be called at end of syn
-            incomingGossipingRequestManager.processReceivedGossipingRequests()
-//            }
-        }.fold(
-                {
-                    isStarting.set(false)
-                    isStarted.set(true)
-                },
-                {
-                    isStarting.set(false)
-                    isStarted.set(false)
-                    Timber.tag(loggerTag.value).e(it, "Start failed")
-                }
-        )
+        isStarting.set(false)
+        isStarted.set(true)
     }
 
     /**
-     * Close the crypto
+     * Close the crypto.
      */
     fun close() = runBlocking(coroutineDispatchers.crypto) {
         cryptoCoroutineScope.coroutineContext.cancelChildren(CancellationException("Closing crypto module"))
-        incomingGossipingRequestManager.close()
+        incomingKeyRequestManager.close()
+        outgoingKeyRequestManager.close()
         olmDevice.release()
         cryptoStore.close()
     }
@@ -426,7 +421,7 @@ internal class DefaultCryptoService @Inject constructor(
     override fun crossSigningService() = crossSigningService
 
     /**
-     * A sync response has been received
+     * A sync response has been received.
      *
      * @param syncResponse the syncResponse
      */
@@ -470,21 +465,34 @@ internal class DefaultCryptoService @Inject constructor(
                     }
 
                     oneTimeKeysUploader.maybeUploadOneTimeKeys()
-                    incomingGossipingRequestManager.processReceivedGossipingRequests()
                 }
-            }
 
-            tryOrNull {
-                gossipingBuffer.toList().let {
-                    cryptoStore.saveGossipingEvents(it)
+                // Process pending key requests
+                try {
+                    if (toDevices.isEmpty()) {
+                        // this is not blocking
+                        outgoingKeyRequestManager.requireProcessAllPendingKeyRequests()
+                    } else {
+                        Timber.tag(loggerTag.value)
+                                .w("Don't process key requests yet as there might be more to_device to catchup")
+                    }
+                } catch (failure: Throwable) {
+                    // just for safety but should not throw
+                    Timber.tag(loggerTag.value).w("failed to process pending request")
+                }
+
+                try {
+                    incomingKeyRequestManager.processIncomingRequests()
+                } catch (failure: Throwable) {
+                    // just for safety but should not throw
+                    Timber.tag(loggerTag.value).w("failed to process incoming room key requests")
                 }
-                gossipingBuffer.clear()
             }
         }
     }
 
     /**
-     * Find a device by curve25519 identity key
+     * Find a device by curve25519 identity key.
      *
      * @param senderKey the curve25519 key to match.
      * @param algorithm the encryption algorithm.
@@ -498,7 +506,7 @@ internal class DefaultCryptoService @Inject constructor(
     }
 
     /**
-     * Provides the device information for a user id and a device Id
+     * Provides the device information for a user id and a device Id.
      *
      * @param userId   the user id
      * @param deviceId the device id
@@ -528,7 +536,7 @@ internal class DefaultCryptoService @Inject constructor(
     }
 
     /**
-     * Set the devices as known
+     * Set the devices as known.
      *
      * @param devices  the devices. Note that the verified member of the devices in this list will not be updated by this method.
      * @param callback the asynchronous callback
@@ -592,7 +600,7 @@ internal class DefaultCryptoService @Inject constructor(
         // (for now at least. Maybe we should alert the user somehow?)
         val existingAlgorithm = cryptoStore.getRoomAlgorithm(roomId)
 
-        if (existingAlgorithm == algorithm && roomEncryptorsStore.get(roomId) != null) {
+        if (existingAlgorithm == algorithm) {
             // ignore
             Timber.tag(loggerTag.value).e("setEncryptionInRoom() : Ignoring m.room.encryption for same alg ($algorithm) in  $roomId")
             return false
@@ -639,7 +647,7 @@ internal class DefaultCryptoService @Inject constructor(
     }
 
     /**
-     * Tells if a room is encrypted with MXCRYPTO_ALGORITHM_MEGOLM
+     * Tells if a room is encrypted with MXCRYPTO_ALGORITHM_MEGOLM.
      *
      * @param roomId the room id
      * @return true if the room is encrypted with algorithm MXCRYPTO_ALGORITHM_MEGOLM
@@ -701,17 +709,19 @@ internal class DefaultCryptoService @Inject constructor(
             }
             val safeAlgorithm = alg
             if (safeAlgorithm != null) {
-                val t0 = System.currentTimeMillis()
+                val t0 = clock.epochMillis()
                 Timber.tag(loggerTag.value).v("encryptEventContent() starts")
                 runCatching {
                     val content = safeAlgorithm.encryptEventContent(eventContent, eventType, userIds)
-                    Timber.tag(loggerTag.value).v("## CRYPTO | encryptEventContent() : succeeds after ${System.currentTimeMillis() - t0} ms")
+                    Timber.tag(loggerTag.value).v("## CRYPTO | encryptEventContent() : succeeds after ${clock.epochMillis() - t0} ms")
                     MXEncryptEventContentResult(content, EventType.ENCRYPTED)
                 }.foldToCallback(callback)
             } else {
                 val algorithm = getEncryptionAlgorithm(roomId)
-                val reason = String.format(MXCryptoError.UNABLE_TO_ENCRYPT_REASON,
-                        algorithm ?: MXCryptoError.NO_MORE_ALGORITHM_REASON)
+                val reason = String.format(
+                        MXCryptoError.UNABLE_TO_ENCRYPT_REASON,
+                        algorithm ?: MXCryptoError.NO_MORE_ALGORITHM_REASON
+                )
                 Timber.tag(loggerTag.value).e("encryptEventContent() : failed $reason")
                 callback.onFailure(Failure.CryptoError(MXCryptoError.Base(MXCryptoError.ErrorType.UNABLE_TO_ENCRYPT, reason)))
             }
@@ -730,7 +740,7 @@ internal class DefaultCryptoService @Inject constructor(
     }
 
     /**
-     * Decrypt an event
+     * Decrypt an event.
      *
      * @param event    the raw event.
      * @param timeline the id of the timeline where the event is decrypted. It is used to prevent replay attack.
@@ -742,7 +752,7 @@ internal class DefaultCryptoService @Inject constructor(
     }
 
     /**
-     * Decrypt an event asynchronously
+     * Decrypt an event asynchronously.
      *
      * @param event    the raw event.
      * @param timeline the id of the timeline where the event is decrypted. It is used to prevent replay attack.
@@ -753,7 +763,7 @@ internal class DefaultCryptoService @Inject constructor(
     }
 
     /**
-     * Decrypt an event
+     * Decrypt an event.
      *
      * @param event    the raw event.
      * @param timeline the id of the timeline where the event is decrypted. It is used to prevent replay attack.
@@ -774,7 +784,7 @@ internal class DefaultCryptoService @Inject constructor(
     }
 
     /**
-     * Handle the 'toDevice' event
+     * Handle the 'toDevice' event.
      *
      * @param event the event
      */
@@ -783,19 +793,25 @@ internal class DefaultCryptoService @Inject constructor(
         cryptoCoroutineScope.launch(coroutineDispatchers.crypto) {
             when (event.getClearType()) {
                 EventType.ROOM_KEY, EventType.FORWARDED_ROOM_KEY -> {
-                    gossipingBuffer.add(event)
                     // Keys are imported directly, not waiting for end of sync
                     onRoomKeyEvent(event)
                 }
-                EventType.REQUEST_SECRET,
+                EventType.REQUEST_SECRET                         -> {
+                    secretShareManager.handleSecretRequest(event)
+                }
                 EventType.ROOM_KEY_REQUEST                       -> {
-                    // save audit trail
-                    gossipingBuffer.add(event)
-                    // Requests are stacked, and will be handled one by one at the end of the sync (onSyncComplete)
-                    incomingGossipingRequestManager.onGossipingRequestEvent(event)
+                    event.getClearContent().toModel()?.let { req ->
+                        // We'll always get these because we send room key requests to
+                        // '*' (ie. 'all devices') which includes the sending device,
+                        // so ignore requests from ourself because apart from it being
+                        // very silly, it won't work because an Olm session cannot send
+                        // messages to itself.
+                        if (req.requestingDeviceId != deviceId) { // ignore self requests
+                            event.senderId?.let { incomingKeyRequestManager.addNewIncomingRequest(it, req) }
+                        }
+                    }
                 }
                 EventType.SEND_SECRET                            -> {
-                    gossipingBuffer.add(event)
                     onSecretSendReceived(event)
                 }
                 EventType.ROOM_KEY_WITHHELD                      -> {
@@ -833,50 +849,38 @@ internal class DefaultCryptoService @Inject constructor(
         val withHeldContent = event.getClearContent().toModel() ?: return Unit.also {
             Timber.tag(loggerTag.value).i("Malformed onKeyWithHeldReceived() : missing fields")
         }
-        Timber.tag(loggerTag.value).i("onKeyWithHeldReceived() received from:${event.senderId}, content <$withHeldContent>")
-        val alg = roomDecryptorProvider.getOrCreateRoomDecryptor(withHeldContent.roomId, withHeldContent.algorithm)
-        if (alg is IMXWithHeldExtension) {
-            alg.onRoomKeyWithHeldEvent(withHeldContent)
-        } else {
-            Timber.tag(loggerTag.value).e("onKeyWithHeldReceived() from:${event.senderId}: Unable to handle WithHeldContent for ${withHeldContent.algorithm}")
-            return
+        val senderId = event.senderId ?: return Unit.also {
+            Timber.tag(loggerTag.value).i("Malformed onKeyWithHeldReceived() : missing fields")
         }
+        withHeldContent.sessionId ?: return
+        withHeldContent.algorithm ?: return
+        withHeldContent.roomId ?: return
+        withHeldContent.senderKey ?: return
+        outgoingKeyRequestManager.onRoomKeyWithHeld(
+                sessionId = withHeldContent.sessionId,
+                algorithm = withHeldContent.algorithm,
+                roomId = withHeldContent.roomId,
+                senderKey = withHeldContent.senderKey,
+                fromDevice = withHeldContent.fromDevice,
+                event = Event(
+                        type = EventType.ROOM_KEY_WITHHELD,
+                        senderId = senderId,
+                        content = event.getClearContent()
+                )
+        )
     }
 
-    private fun onSecretSendReceived(event: Event) {
-        Timber.tag(loggerTag.value).i("GOSSIP onSecretSend() from ${event.senderId} : onSecretSendReceived ${event.content?.get("sender_key")}")
-        if (!event.isEncrypted()) {
-            // secret send messages must be encrypted
-            Timber.tag(loggerTag.value).e("GOSSIP onSecretSend() :Received unencrypted secret send event")
-            return
-        }
-
-        // Was that sent by us?
-        if (event.senderId != userId) {
-            Timber.tag(loggerTag.value).e("GOSSIP onSecretSend() : Ignore secret from other user ${event.senderId}")
-            return
-        }
-
-        val secretContent = event.getClearContent().toModel() ?: return
-
-        val existingRequest = cryptoStore
-                .getOutgoingSecretKeyRequests().firstOrNull { it.requestId == secretContent.requestId }
-
-        if (existingRequest == null) {
-            Timber.tag(loggerTag.value).i("GOSSIP onSecretSend() : Ignore secret that was not requested: ${secretContent.requestId}")
-            return
-        }
-
-        if (!handleSDKLevelGossip(existingRequest.secretName, secretContent.secretValue)) {
-            // TODO Ask to application layer?
-            Timber.tag(loggerTag.value).v("onSecretSend() : secret not handled by SDK")
+    private suspend fun onSecretSendReceived(event: Event) {
+        secretShareManager.onSecretSendReceived(event) { secretName, secretValue ->
+            handleSDKLevelGossip(secretName, secretValue)
         }
     }
 
     /**
-     * Returns true if handled by SDK, otherwise should be sent to application layer
+     * Returns true if handled by SDK, otherwise should be sent to application layer.
      */
-    private fun handleSDKLevelGossip(secretName: String?, secretValue: String): Boolean {
+    private fun handleSDKLevelGossip(secretName: String?,
+                                     secretValue: String): Boolean {
         return when (secretName) {
             MASTER_KEY_SSSS_NAME       -> {
                 crossSigningService.onSecretMSKGossip(secretValue)
@@ -980,7 +984,7 @@ internal class DefaultCryptoService @Inject constructor(
     }
 
     /**
-     * Export the crypto keys
+     * Export the crypto keys.
      *
      * @param password the password
      * @return the exported keys
@@ -990,7 +994,7 @@ internal class DefaultCryptoService @Inject constructor(
     }
 
     /**
-     * Export the crypto keys
+     * Export the crypto keys.
      *
      * @param password         the password
      * @param anIterationCount the encryption iteration count (0 means no encryption)
@@ -1009,7 +1013,7 @@ internal class DefaultCryptoService @Inject constructor(
     }
 
     /**
-     * Import the room keys
+     * Import the room keys.
      *
      * @param roomKeysAsArray  the room keys as array.
      * @param password         the password
@@ -1022,9 +1026,9 @@ internal class DefaultCryptoService @Inject constructor(
         return withContext(coroutineDispatchers.crypto) {
             Timber.tag(loggerTag.value).v("importRoomKeys starts")
 
-            val t0 = System.currentTimeMillis()
+            val t0 = clock.epochMillis()
             val roomKeys = MXMegolmExportEncryption.decryptMegolmKeyFile(roomKeysAsArray, password)
-            val t1 = System.currentTimeMillis()
+            val t1 = clock.epochMillis()
 
             Timber.tag(loggerTag.value).v("importRoomKeys : decryptMegolmKeyFile done in ${t1 - t0} ms")
 
@@ -1032,7 +1036,7 @@ internal class DefaultCryptoService @Inject constructor(
                     .adapter>(Types.newParameterizedType(List::class.java, MegolmSessionData::class.java))
                     .fromJson(roomKeys)
 
-            val t2 = System.currentTimeMillis()
+            val t2 = clock.epochMillis()
 
             Timber.tag(loggerTag.value).v("importRoomKeys : JSON parsing ${t2 - t1} ms")
 
@@ -1091,6 +1095,12 @@ internal class DefaultCryptoService @Inject constructor(
         cryptoStore.setGlobalBlacklistUnverifiedDevices(block)
     }
 
+    override fun enableKeyGossiping(enable: Boolean) {
+        cryptoStore.enableKeyGossiping(enable)
+    }
+
+    override fun isKeyGossipingEnabled() = cryptoStore.isKeyGossipingEnabled()
+
     /**
      * Tells whether the client should ever send encrypted messages to unverified devices.
      * The default value is false.
@@ -1154,52 +1164,17 @@ internal class DefaultCryptoService @Inject constructor(
         setRoomBlacklistUnverifiedDevices(roomId, false)
     }
 
-// TODO Check if this method is still necessary
-    /**
-     * Cancel any earlier room key request
-     *
-     * @param requestBody requestBody
-     */
-    override fun cancelRoomKeyRequest(requestBody: RoomKeyRequestBody) {
-        outgoingGossipingRequestManager.cancelRoomKeyRequest(requestBody)
-    }
-
     /**
      * Re request the encryption keys required to decrypt an event.
      *
      * @param event the event to decrypt again.
      */
     override fun reRequestRoomKeyForEvent(event: Event) {
-        val wireContent = event.content.toModel() ?: return Unit.also {
-            Timber.tag(loggerTag.value).e("reRequestRoomKeyForEvent Failed to re-request key, null content")
-        }
-
-        val requestBody = RoomKeyRequestBody(
-                algorithm = wireContent.algorithm,
-                roomId = event.roomId,
-                senderKey = wireContent.senderKey,
-                sessionId = wireContent.sessionId
-        )
-
-        outgoingGossipingRequestManager.resendRoomKeyRequest(requestBody)
+        outgoingKeyRequestManager.requestKeyForEvent(event, true)
     }
 
     override fun requestRoomKeyForEvent(event: Event) {
-        val wireContent = event.content.toModel() ?: return Unit.also {
-            Timber.tag(loggerTag.value).e("requestRoomKeyForEvent Failed to request key, null content eventId: ${event.eventId}")
-        }
-
-        cryptoCoroutineScope.launch(coroutineDispatchers.crypto) {
-//            if (!isStarted()) {
-//                Timber.v("## CRYPTO | requestRoomKeyForEvent() : wait after e2e init")
-//                internalStart(false)
-//            }
-            roomDecryptorProvider
-                    .getOrCreateRoomDecryptor(event.roomId, wireContent.algorithm)
-                    ?.requestKeysForEvent(event, false) ?: run {
-                Timber.tag(loggerTag.value).v("requestRoomKeyForEvent() : No room decryptor for roomId:${event.roomId} algorithm:${wireContent.algorithm}")
-            }
-        }
+        outgoingKeyRequestManager.requestKeyForEvent(event, false)
     }
 
     /**
@@ -1208,7 +1183,8 @@ internal class DefaultCryptoService @Inject constructor(
      * @param listener listener
      */
     override fun addRoomKeysRequestListener(listener: GossipingRequestListener) {
-        incomingGossipingRequestManager.addRoomKeysRequestListener(listener)
+        incomingKeyRequestManager.addRoomKeysRequestListener(listener)
+        secretShareManager.addListener(listener)
     }
 
     /**
@@ -1217,44 +1193,12 @@ internal class DefaultCryptoService @Inject constructor(
      * @param listener listener
      */
     override fun removeRoomKeysRequestListener(listener: GossipingRequestListener) {
-        incomingGossipingRequestManager.removeRoomKeysRequestListener(listener)
+        incomingKeyRequestManager.removeRoomKeysRequestListener(listener)
+        secretShareManager.removeListener(listener)
     }
 
-//    private fun markOlmSessionForUnwedging(senderId: String, deviceInfo: CryptoDeviceInfo) {
-//        val deviceKey = deviceInfo.identityKey()
-//
-//        val lastForcedDate = lastNewSessionForcedDates.getObject(senderId, deviceKey) ?: 0
-//        val now = System.currentTimeMillis()
-//        if (now - lastForcedDate < CRYPTO_MIN_FORCE_SESSION_PERIOD_MILLIS) {
-//            Timber.d("## CRYPTO | markOlmSessionForUnwedging: New session already forced with device at $lastForcedDate. Not forcing another")
-//            return
-//        }
-//
-//        Timber.d("## CRYPTO | markOlmSessionForUnwedging from $senderId:${deviceInfo.deviceId}")
-//        lastNewSessionForcedDates.setObject(senderId, deviceKey, now)
-//
-//        cryptoCoroutineScope.launch(coroutineDispatchers.crypto) {
-//            ensureOlmSessionsForDevicesAction.handle(mapOf(senderId to listOf(deviceInfo)), force = true)
-//
-//            // Now send a blank message on that session so the other side knows about it.
-//            // (The keyshare request is sent in the clear so that won't do)
-//            // We send this first such that, as long as the toDevice messages arrive in the
-//            // same order we sent them, the other end will get this first, set up the new session,
-//            // then get the keyshare request and send the key over this new session (because it
-//            // is the session it has most recently received a message on).
-//            val payloadJson = mapOf("type" to EventType.DUMMY)
-//
-//            val encodedPayload = messageEncrypter.encryptMessage(payloadJson, listOf(deviceInfo))
-//            val sendToDeviceMap = MXUsersDevicesMap()
-//            sendToDeviceMap.setObject(senderId, deviceInfo.deviceId, encodedPayload)
-//            Timber.v("## CRYPTO | markOlmSessionForUnwedging() : sending to $senderId:${deviceInfo.deviceId}")
-//            val sendToDeviceParams = SendToDeviceTask.Params(EventType.ENCRYPTED, sendToDeviceMap)
-//            sendToDeviceTask.execute(sendToDeviceParams)
-//        }
-//    }
-
     /**
-     * Provides the list of unknown devices
+     * Provides the list of unknown devices.
      *
      * @param devicesInRoom the devices map
      * @return the unknown devices map
@@ -1298,27 +1242,41 @@ internal class DefaultCryptoService @Inject constructor(
         return "DefaultCryptoService of $userId ($deviceId)"
     }
 
-    override fun getOutgoingRoomKeyRequests(): List {
+    override fun getOutgoingRoomKeyRequests(): List {
         return cryptoStore.getOutgoingRoomKeyRequests()
     }
 
-    override fun getOutgoingRoomKeyRequestsPaged(): LiveData> {
+    override fun getOutgoingRoomKeyRequestsPaged(): LiveData> {
         return cryptoStore.getOutgoingRoomKeyRequestsPaged()
     }
 
-    override fun getIncomingRoomKeyRequestsPaged(): LiveData> {
-        return cryptoStore.getIncomingRoomKeyRequestsPaged()
-    }
-
     override fun getIncomingRoomKeyRequests(): List {
-        return cryptoStore.getIncomingRoomKeyRequests()
+        return cryptoStore.getGossipingEvents()
+                .mapNotNull {
+                    IncomingRoomKeyRequest.fromEvent(it)
+                }
     }
 
-    override fun getGossipingEventsTrail(): LiveData> {
+    override fun getIncomingRoomKeyRequestsPaged(): LiveData> {
+        return cryptoStore.getGossipingEventsTrail(TrailType.IncomingKeyRequest) {
+            IncomingRoomKeyRequest.fromEvent(it)
+                    ?: IncomingRoomKeyRequest(localCreationTimestamp = 0L)
+        }
+    }
+
+    /**
+     * If you registered a `GossipingRequestListener`, you will be notified of key request
+     * that was not accepted by the SDK. You can call back this manually to accept anyhow.
+     */
+    override suspend fun manuallyAcceptRoomKeyRequest(request: IncomingRoomKeyRequest) {
+        incomingKeyRequestManager.manuallyAcceptRoomKeyRequest(request)
+    }
+
+    override fun getGossipingEventsTrail(): LiveData> {
         return cryptoStore.getGossipingEventsTrail()
     }
 
-    override fun getGossipingEvents(): List {
+    override fun getGossipingEvents(): List {
         return cryptoStore.getGossipingEvents()
     }
 
@@ -1342,8 +1300,8 @@ internal class DefaultCryptoService @Inject constructor(
                 loadRoomMembersTask.execute(LoadRoomMembersTask.Params(roomId))
             } catch (failure: Throwable) {
                 Timber.tag(loggerTag.value).e("prepareToEncrypt() : Failed to load room members")
-                callback.onFailure(failure)
-                return@launch
+                // we probably shouldn't block sending on that (but questionable)
+                // but some members won't be able to decrypt
             }
 
             val userIds = getRoomUserIds(roomId)
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/DeviceListManager.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/DeviceListManager.kt
index 6cae2d0935..cd4e2a6d52 100755
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/DeviceListManager.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/DeviceListManager.kt
@@ -31,19 +31,23 @@ import org.matrix.android.sdk.internal.session.SessionScope
 import org.matrix.android.sdk.internal.session.sync.SyncTokenStore
 import org.matrix.android.sdk.internal.task.TaskExecutor
 import org.matrix.android.sdk.internal.util.logLimit
+import org.matrix.android.sdk.internal.util.time.Clock
 import timber.log.Timber
 import javax.inject.Inject
 
 // Legacy name: MXDeviceList
 @SessionScope
-internal class DeviceListManager @Inject constructor(private val cryptoStore: IMXCryptoStore,
-                                                     private val olmDevice: MXOlmDevice,
-                                                     private val syncTokenStore: SyncTokenStore,
-                                                     private val credentials: Credentials,
-                                                     private val downloadKeysForUsersTask: DownloadKeysForUsersTask,
-                                                     private val cryptoSessionInfoProvider: CryptoSessionInfoProvider,
-                                                     coroutineDispatchers: MatrixCoroutineDispatchers,
-                                                     private val taskExecutor: TaskExecutor) {
+internal class DeviceListManager @Inject constructor(
+        private val cryptoStore: IMXCryptoStore,
+        private val olmDevice: MXOlmDevice,
+        private val syncTokenStore: SyncTokenStore,
+        private val credentials: Credentials,
+        private val downloadKeysForUsersTask: DownloadKeysForUsersTask,
+        private val cryptoSessionInfoProvider: CryptoSessionInfoProvider,
+        coroutineDispatchers: MatrixCoroutineDispatchers,
+        private val taskExecutor: TaskExecutor,
+        private val clock: Clock,
+) {
 
     interface UserDevicesUpdateListener {
         fun onUsersDeviceUpdate(userIds: List)
@@ -98,7 +102,7 @@ internal class DeviceListManager @Inject constructor(private val cryptoStore: IM
     }
 
     /**
-     * Tells if the key downloads should be tried
+     * Tells if the key downloads should be tried.
      *
      * @param userId the userId
      * @return true if the keys download can be retrieved
@@ -120,7 +124,7 @@ internal class DeviceListManager @Inject constructor(private val cryptoStore: IM
     }
 
     /**
-     * Clear the unavailable server lists
+     * Clear the unavailable server lists.
      */
     private fun clearUnavailableServersList() {
         synchronized(notReadyToRetryHS) {
@@ -163,7 +167,7 @@ internal class DeviceListManager @Inject constructor(private val cryptoStore: IM
     }
 
     /**
-     * Update the devices list statuses
+     * Update the devices list statuses.
      *
      * @param changed the user ids list which have new devices
      * @param left    the user ids list which left a room
@@ -199,15 +203,14 @@ internal class DeviceListManager @Inject constructor(private val cryptoStore: IM
     }
 
     /**
-     * This will flag each user whose devices we are tracking as in need of an
-     * + update
+     * This will flag each user whose devices we are tracking as in need of an update.
      */
     fun invalidateAllDeviceLists() {
         handleDeviceListsChanges(cryptoStore.getDeviceTrackingStatuses().keys, emptyList())
     }
 
     /**
-     * The keys download failed
+     * The keys download failed.
      *
      * @param userIds the user ids list
      */
@@ -310,11 +313,20 @@ internal class DeviceListManager @Inject constructor(private val cryptoStore: IM
             stored
         } else {
             Timber.v("## CRYPTO | downloadKeys() : starts")
-            val t0 = System.currentTimeMillis()
-            val result = doKeyDownloadForUsers(downloadUsers)
-            Timber.v("## CRYPTO | downloadKeys() : doKeyDownloadForUsers succeeds after ${System.currentTimeMillis() - t0} ms")
-            result.also {
-                it.addEntriesFromMap(stored)
+            val t0 = clock.epochMillis()
+            try {
+                val result = doKeyDownloadForUsers(downloadUsers)
+                Timber.v("## CRYPTO | downloadKeys() : doKeyDownloadForUsers succeeds after ${clock.epochMillis() - t0} ms")
+                result.also {
+                    it.addEntriesFromMap(stored)
+                }
+            } catch (failure: Throwable) {
+                Timber.w(failure, "## CRYPTO | downloadKeys() : doKeyDownloadForUsers failed after ${clock.epochMillis() - t0} ms")
+                if (forceDownload) {
+                    throw failure
+                } else {
+                    stored
+                }
             }
         }
     }
@@ -475,8 +487,10 @@ internal class DeviceListManager @Inject constructor(private val cryptoStore: IM
         }
 
         if (!isVerified) {
-            Timber.e("## CRYPTO | validateDeviceKeys() : Unable to verify signature on device " + userId + ":" +
-                    deviceKeys.deviceId + " with error " + errorMessage)
+            Timber.e(
+                    "## CRYPTO | validateDeviceKeys() : Unable to verify signature on device " + userId + ":" +
+                            deviceKeys.deviceId + " with error " + errorMessage
+            )
             return false
         }
 
@@ -486,9 +500,11 @@ internal class DeviceListManager @Inject constructor(private val cryptoStore: IM
                 // best off sticking with the original keys.
                 //
                 // Should we warn the user about it somehow?
-                Timber.e("## CRYPTO | validateDeviceKeys() : WARNING:Ed25519 key for device " + userId + ":" +
-                        deviceKeys.deviceId + " has changed : " +
-                        previouslyStoredDeviceKeys.fingerprint() + " -> " + signKey)
+                Timber.e(
+                        "## CRYPTO | validateDeviceKeys() : WARNING:Ed25519 key for device " + userId + ":" +
+                                deviceKeys.deviceId + " has changed : " +
+                                previouslyStoredDeviceKeys.fingerprint() + " -> " + signKey
+                )
 
                 Timber.e("## CRYPTO | validateDeviceKeys() : $previouslyStoredDeviceKeys -> $deviceKeys")
                 Timber.e("## CRYPTO | validateDeviceKeys() : ${previouslyStoredDeviceKeys.keys} -> ${deviceKeys.keys}")
@@ -535,7 +551,7 @@ internal class DeviceListManager @Inject constructor(private val cryptoStore: IM
     companion object {
 
         /**
-         * State transition diagram for DeviceList.deviceTrackingStatus
+         * State transition diagram for DeviceList.deviceTrackingStatus.
          * 
          *
          *                                   |
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/EventDecryptor.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/EventDecryptor.kt
index 1c8bce7377..d6f881211c 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/EventDecryptor.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/EventDecryptor.kt
@@ -36,6 +36,7 @@ import org.matrix.android.sdk.internal.crypto.store.IMXCryptoStore
 import org.matrix.android.sdk.internal.crypto.tasks.SendToDeviceTask
 import org.matrix.android.sdk.internal.extensions.foldToCallback
 import org.matrix.android.sdk.internal.session.SessionScope
+import org.matrix.android.sdk.internal.util.time.Clock
 import timber.log.Timber
 import javax.inject.Inject
 
@@ -47,6 +48,7 @@ private val loggerTag = LoggerTag("CryptoSyncHandler", LoggerTag.CRYPTO)
 internal class EventDecryptor @Inject constructor(
         private val cryptoCoroutineScope: CoroutineScope,
         private val coroutineDispatchers: MatrixCoroutineDispatchers,
+        private val clock: Clock,
         private val roomDecryptorProvider: RoomDecryptorProvider,
         private val messageEncrypter: MessageEncrypter,
         private val sendToDeviceTask: SendToDeviceTask,
@@ -68,7 +70,7 @@ internal class EventDecryptor @Inject constructor(
     private val wedgedDevices = mutableListOf()
 
     /**
-     * Decrypt an event
+     * Decrypt an event.
      *
      * @param event    the raw event.
      * @param timeline the id of the timeline where the event is decrypted. It is used to prevent replay attack.
@@ -80,7 +82,7 @@ internal class EventDecryptor @Inject constructor(
     }
 
     /**
-     * Decrypt an event asynchronously
+     * Decrypt an event asynchronously.
      *
      * @param event    the raw event.
      * @param timeline the id of the timeline where the event is decrypted. It is used to prevent replay attack.
@@ -96,7 +98,7 @@ internal class EventDecryptor @Inject constructor(
     }
 
     /**
-     * Decrypt an event
+     * Decrypt an event.
      *
      * @param event    the raw event.
      * @param timeline the id of the timeline where the event is decrypted. It is used to prevent replay attack.
@@ -153,7 +155,7 @@ internal class EventDecryptor @Inject constructor(
         // we should force start a new session for those
         Timber.tag(loggerTag.value).v("Unwedging:  ${wedgedDevices.size} are wedged")
         // get the one that should be retried according to rate limit
-        val now = System.currentTimeMillis()
+        val now = clock.epochMillis()
         val toUnwedge = wedgedDevices.filter {
             val lastForcedDate = lastNewSessionForcedDates[it] ?: 0
             if (now - lastForcedDate < DefaultCryptoService.CRYPTO_MIN_FORCE_SESSION_PERIOD_MILLIS) {
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/GossipingWorkManager.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/GossipingWorkManager.kt
deleted file mode 100644
index 0013c31eea..0000000000
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/GossipingWorkManager.kt
+++ /dev/null
@@ -1,57 +0,0 @@
-/*
- * Copyright 2020 The Matrix.org Foundation C.I.C.
- *
- * 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 org.matrix.android.sdk.internal.crypto
-
-import androidx.work.BackoffPolicy
-import androidx.work.Data
-import androidx.work.ExistingWorkPolicy
-import androidx.work.ListenableWorker
-import androidx.work.OneTimeWorkRequest
-import org.matrix.android.sdk.api.util.Cancelable
-import org.matrix.android.sdk.internal.di.WorkManagerProvider
-import org.matrix.android.sdk.internal.session.SessionScope
-import org.matrix.android.sdk.internal.util.CancelableWork
-import org.matrix.android.sdk.internal.worker.startChain
-import java.util.concurrent.TimeUnit
-import javax.inject.Inject
-
-@SessionScope
-internal class GossipingWorkManager @Inject constructor(
-        private val workManagerProvider: WorkManagerProvider
-) {
-
-    inline fun  createWork(data: Data, startChain: Boolean): OneTimeWorkRequest {
-        return workManagerProvider.matrixOneTimeWorkRequestBuilder()
-                .setConstraints(WorkManagerProvider.workConstraints)
-                .startChain(startChain)
-                .setInputData(data)
-                .setBackoffCriteria(BackoffPolicy.LINEAR, WorkManagerProvider.BACKOFF_DELAY_MILLIS, TimeUnit.MILLISECONDS)
-                .build()
-    }
-
-    // Prevent sending queue to stay broken after app restart
-    // The unique queue id will stay the same as long as this object is instanciated
-    val queueSuffixApp = System.currentTimeMillis()
-
-    fun postWork(workRequest: OneTimeWorkRequest, policy: ExistingWorkPolicy = ExistingWorkPolicy.APPEND): Cancelable {
-        workManagerProvider.workManager
-                .beginUniqueWork(this::class.java.name + "_$queueSuffixApp", policy, workRequest)
-                .enqueue()
-
-        return CancelableWork(workManagerProvider.workManager, workRequest.id)
-    }
-}
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/InboundGroupSessionStore.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/InboundGroupSessionStore.kt
index a78444dff9..28ddf291b2 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/InboundGroupSessionStore.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/InboundGroupSessionStore.kt
@@ -75,15 +75,15 @@ internal class InboundGroupSessionStore @Inject constructor(
 
     @Synchronized
     fun getInboundGroupSession(sessionId: String, senderKey: String): InboundGroupSessionHolder? {
-            val known = sessionCache[CacheKey(sessionId, senderKey)]
-            Timber.tag(loggerTag.value).v("## Inbound: getInboundGroupSession  $sessionId in cache ${known != null}")
-            return known
-                    ?: store.getInboundGroupSession(sessionId, senderKey)?.also {
-                Timber.tag(loggerTag.value).v("## Inbound: getInboundGroupSession cache populate ${it.roomId}")
-                sessionCache.put(CacheKey(sessionId, senderKey), InboundGroupSessionHolder(it))
-            }?.let {
-                InboundGroupSessionHolder(it)
-            }
+        val known = sessionCache[CacheKey(sessionId, senderKey)]
+        Timber.tag(loggerTag.value).v("## Inbound: getInboundGroupSession  $sessionId in cache ${known != null}")
+        return known
+                ?: store.getInboundGroupSession(sessionId, senderKey)?.also {
+                    Timber.tag(loggerTag.value).v("## Inbound: getInboundGroupSession cache populate ${it.roomId}")
+                    sessionCache.put(CacheKey(sessionId, senderKey), InboundGroupSessionHolder(it))
+                }?.let {
+                    InboundGroupSessionHolder(it)
+                }
     }
 
     @Synchronized
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/IncomingGossipingRequestManager.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/IncomingGossipingRequestManager.kt
deleted file mode 100644
index 3a409cf3fd..0000000000
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/IncomingGossipingRequestManager.kt
+++ /dev/null
@@ -1,472 +0,0 @@
-/*
- * Copyright 2020 The Matrix.org Foundation C.I.C.
- *
- * 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 org.matrix.android.sdk.internal.crypto
-
-import kotlinx.coroutines.CoroutineScope
-import kotlinx.coroutines.launch
-import org.matrix.android.sdk.api.MatrixCoroutineDispatchers
-import org.matrix.android.sdk.api.auth.data.Credentials
-import org.matrix.android.sdk.api.crypto.MXCRYPTO_ALGORITHM_MEGOLM
-import org.matrix.android.sdk.api.crypto.MXCryptoConfig
-import org.matrix.android.sdk.api.session.crypto.crosssigning.KEYBACKUP_SECRET_SSSS_NAME
-import org.matrix.android.sdk.api.session.crypto.crosssigning.MASTER_KEY_SSSS_NAME
-import org.matrix.android.sdk.api.session.crypto.crosssigning.SELF_SIGNING_KEY_SSSS_NAME
-import org.matrix.android.sdk.api.session.crypto.crosssigning.USER_SIGNING_KEY_SSSS_NAME
-import org.matrix.android.sdk.api.session.crypto.keysbackup.extractCurveKeyFromRecoveryKey
-import org.matrix.android.sdk.api.session.crypto.keyshare.GossipingRequestListener
-import org.matrix.android.sdk.api.session.crypto.model.GossipingRequestState
-import org.matrix.android.sdk.api.session.crypto.model.GossipingToDeviceObject
-import org.matrix.android.sdk.api.session.crypto.model.IncomingRequestCancellation
-import org.matrix.android.sdk.api.session.crypto.model.IncomingRoomKeyRequest
-import org.matrix.android.sdk.api.session.crypto.model.IncomingSecretShareRequest
-import org.matrix.android.sdk.api.session.crypto.model.RoomKeyRequestBody
-import org.matrix.android.sdk.api.session.events.model.Event
-import org.matrix.android.sdk.api.session.events.model.EventType
-import org.matrix.android.sdk.api.session.events.model.toModel
-import org.matrix.android.sdk.api.util.toBase64NoPadding
-import org.matrix.android.sdk.internal.crypto.algorithms.IMXGroupEncryption
-import org.matrix.android.sdk.internal.crypto.model.rest.GossipingDefaultContent
-import org.matrix.android.sdk.internal.crypto.store.IMXCryptoStore
-import org.matrix.android.sdk.internal.crypto.tasks.createUniqueTxnId
-import org.matrix.android.sdk.internal.di.SessionId
-import org.matrix.android.sdk.internal.session.SessionScope
-import org.matrix.android.sdk.internal.worker.WorkerParamsFactory
-import timber.log.Timber
-import java.util.concurrent.Executors
-import javax.inject.Inject
-
-@SessionScope
-internal class IncomingGossipingRequestManager @Inject constructor(
-        @SessionId private val sessionId: String,
-        private val credentials: Credentials,
-        private val cryptoStore: IMXCryptoStore,
-        private val cryptoConfig: MXCryptoConfig,
-        private val gossipingWorkManager: GossipingWorkManager,
-        private val roomEncryptorsStore: RoomEncryptorsStore,
-        private val roomDecryptorProvider: RoomDecryptorProvider,
-        private val coroutineDispatchers: MatrixCoroutineDispatchers,
-        private val cryptoCoroutineScope: CoroutineScope) {
-
-    private val executor = Executors.newSingleThreadExecutor()
-
-    // list of IncomingRoomKeyRequests/IncomingRoomKeyRequestCancellations
-    // we received in the current sync.
-    private val receivedGossipingRequests = ArrayList()
-    private val receivedRequestCancellations = ArrayList()
-
-    // the listeners
-    private val gossipingRequestListeners: MutableSet = HashSet()
-
-    init {
-        receivedGossipingRequests.addAll(cryptoStore.getPendingIncomingGossipingRequests())
-    }
-
-    fun close() {
-        executor.shutdownNow()
-    }
-
-    // Recently verified devices (map of deviceId and timestamp)
-    private val recentlyVerifiedDevices = HashMap()
-
-    /**
-     * Called when a session has been verified.
-     * This information can be used by the manager to decide whether or not to fullfil gossiping requests
-     */
-    fun onVerificationCompleteForDevice(deviceId: String) {
-        // For now we just keep an in memory cache
-        synchronized(recentlyVerifiedDevices) {
-            recentlyVerifiedDevices[deviceId] = System.currentTimeMillis()
-        }
-    }
-
-    private fun hasBeenVerifiedLessThanFiveMinutesFromNow(deviceId: String): Boolean {
-        val verifTimestamp: Long?
-        synchronized(recentlyVerifiedDevices) {
-            verifTimestamp = recentlyVerifiedDevices[deviceId]
-        }
-        if (verifTimestamp == null) return false
-
-        val age = System.currentTimeMillis() - verifTimestamp
-
-        return age < FIVE_MINUTES_IN_MILLIS
-    }
-
-    /**
-     * Called when we get an m.room_key_request event
-     * It must be called on CryptoThread
-     *
-     * @param event the announcement event.
-     */
-    fun onGossipingRequestEvent(event: Event) {
-        val roomKeyShare = event.getClearContent().toModel()
-        Timber.i("## CRYPTO | GOSSIP onGossipingRequestEvent received type ${event.type} from user:${event.senderId}, content:$roomKeyShare")
-        // val ageLocalTs = event.unsignedData?.age?.let { System.currentTimeMillis() - it }
-        when (roomKeyShare?.action) {
-            GossipingToDeviceObject.ACTION_SHARE_REQUEST -> {
-                if (event.getClearType() == EventType.REQUEST_SECRET) {
-                    IncomingSecretShareRequest.fromEvent(event)?.let {
-                        if (event.senderId == credentials.userId && it.deviceId == credentials.deviceId) {
-                            // ignore, it was sent by me as *
-                            Timber.v("## GOSSIP onGossipingRequestEvent type ${event.type} ignore remote echo")
-                        } else {
-//                            // save in DB
-//                            cryptoStore.storeIncomingGossipingRequest(it, ageLocalTs)
-                            receivedGossipingRequests.add(it)
-                        }
-                    }
-                } else if (event.getClearType() == EventType.ROOM_KEY_REQUEST) {
-                    IncomingRoomKeyRequest.fromEvent(event)?.let {
-                        if (event.senderId == credentials.userId && it.deviceId == credentials.deviceId) {
-                            // ignore, it was sent by me as *
-                            Timber.v("## GOSSIP onGossipingRequestEvent type ${event.type} ignore remote echo")
-                        } else {
-//                            cryptoStore.storeIncomingGossipingRequest(it, ageLocalTs)
-                            receivedGossipingRequests.add(it)
-                        }
-                    }
-                }
-            }
-            GossipingToDeviceObject.ACTION_SHARE_CANCELLATION -> {
-                IncomingRequestCancellation.fromEvent(event)?.let {
-                    receivedRequestCancellations.add(it)
-                }
-            }
-            else                                              -> {
-                Timber.e("## GOSSIP onGossipingRequestEvent() : unsupported action ${roomKeyShare?.action}")
-            }
-        }
-    }
-
-    /**
-     * Process any m.room_key_request or m.secret.request events which were queued up during the
-     * current sync.
-     * It must be called on CryptoThread
-     */
-    fun processReceivedGossipingRequests() {
-        val roomKeyRequestsToProcess = receivedGossipingRequests.toList()
-        receivedGossipingRequests.clear()
-
-        Timber.v("## CRYPTO | GOSSIP processReceivedGossipingRequests() : ${roomKeyRequestsToProcess.size} request to process")
-
-        var receivedRequestCancellations: List? = null
-
-        synchronized(this.receivedRequestCancellations) {
-            if (this.receivedRequestCancellations.isNotEmpty()) {
-                receivedRequestCancellations = this.receivedRequestCancellations.toList()
-                this.receivedRequestCancellations.clear()
-            }
-        }
-
-        executor.execute {
-            cryptoStore.storeIncomingGossipingRequests(roomKeyRequestsToProcess)
-            for (request in roomKeyRequestsToProcess) {
-                if (request is IncomingRoomKeyRequest) {
-                    processIncomingRoomKeyRequest(request)
-                } else if (request is IncomingSecretShareRequest) {
-                    processIncomingSecretShareRequest(request)
-                }
-            }
-
-            receivedRequestCancellations?.forEach { request ->
-                Timber.v("## CRYPTO | GOSSIP processReceivedGossipingRequests() : m.room_key_request cancellation $request")
-                // we should probably only notify the app of cancellations we told it
-                // about, but we don't currently have a record of that, so we just pass
-                // everything through.
-                if (request.userId == credentials.userId && request.deviceId == credentials.deviceId) {
-                    // ignore remote echo
-                    return@forEach
-                }
-                val matchingIncoming = cryptoStore.getIncomingRoomKeyRequest(request.userId ?: "", request.deviceId ?: "", request.requestId ?: "")
-                if (matchingIncoming == null) {
-                    // ignore that?
-                    return@forEach
-                } else {
-                    // If it was accepted from this device, keep the information, do not mark as cancelled
-                    if (matchingIncoming.state != GossipingRequestState.ACCEPTED) {
-                        onRoomKeyRequestCancellation(request)
-                        cryptoStore.updateGossipingRequestState(request, GossipingRequestState.CANCELLED_BY_REQUESTER)
-                    }
-                }
-            }
-        }
-    }
-
-    private fun processIncomingRoomKeyRequest(request: IncomingRoomKeyRequest) {
-        val userId = request.userId ?: return
-        val deviceId = request.deviceId ?: return
-        val body = request.requestBody ?: return
-        val roomId = body.roomId ?: return
-        val alg = body.algorithm ?: return
-
-        Timber.v("## CRYPTO | GOSSIP processIncomingRoomKeyRequest from $userId:$deviceId for $roomId / ${body.sessionId} id ${request.requestId}")
-        if (credentials.userId != userId) {
-            handleKeyRequestFromOtherUser(body, request, alg, roomId, userId, deviceId)
-            return
-        }
-        // TODO: should we queue up requests we don't yet have keys for, in case they turn up later?
-        // if we don't have a decryptor for this room/alg, we don't have
-        // the keys for the requested events, and can drop the requests.
-        val decryptor = roomDecryptorProvider.getRoomDecryptor(roomId, alg)
-        if (null == decryptor) {
-            Timber.w("## CRYPTO | GOSSIP processReceivedGossipingRequests() : room key request for unknown $alg in room $roomId")
-            cryptoStore.updateGossipingRequestState(request, GossipingRequestState.REJECTED)
-            return
-        }
-        if (!decryptor.hasKeysForKeyRequest(request)) {
-            Timber.w("## CRYPTO | GOSSIP processReceivedGossipingRequests() : room key request for unknown session ${body.sessionId!!}")
-            cryptoStore.updateGossipingRequestState(request, GossipingRequestState.REJECTED)
-            return
-        }
-
-        if (credentials.deviceId == deviceId && credentials.userId == userId) {
-            Timber.v("## CRYPTO | GOSSIP processReceivedGossipingRequests() : oneself device - ignored")
-            cryptoStore.updateGossipingRequestState(request, GossipingRequestState.REJECTED)
-            return
-        }
-        request.share = Runnable {
-            decryptor.shareKeysWithDevice(request)
-            cryptoStore.updateGossipingRequestState(request, GossipingRequestState.ACCEPTED)
-        }
-        request.ignore = Runnable {
-            cryptoStore.updateGossipingRequestState(request, GossipingRequestState.REJECTED)
-        }
-        // if the device is verified already, share the keys
-        val device = cryptoStore.getUserDevice(userId, deviceId)
-        if (device != null) {
-            if (device.isVerified) {
-                Timber.v("## CRYPTO | GOSSIP processReceivedGossipingRequests() : device is already verified: sharing keys")
-                request.share?.run()
-                return
-            }
-
-            if (device.isBlocked) {
-                Timber.v("## CRYPTO | GOSSIP processReceivedGossipingRequests() : device is blocked -> ignored")
-                cryptoStore.updateGossipingRequestState(request, GossipingRequestState.REJECTED)
-                return
-            }
-        }
-
-        // As per config we automatically discard untrusted devices request
-        if (cryptoConfig.discardRoomKeyRequestsFromUntrustedDevices) {
-            Timber.v("## CRYPTO | processReceivedGossipingRequests() : discardRoomKeyRequestsFromUntrustedDevices")
-            // At this point the device is unknown, we don't want to bother user with that
-            cryptoStore.updateGossipingRequestState(request, GossipingRequestState.REJECTED)
-            return
-        }
-
-        // Pass to application layer to decide what to do
-        onRoomKeyRequest(request)
-    }
-
-    private fun handleKeyRequestFromOtherUser(body: RoomKeyRequestBody,
-                                              request: IncomingRoomKeyRequest,
-                                              alg: String,
-                                              roomId: String,
-                                              userId: String,
-                                              deviceId: String) {
-        Timber.w("## CRYPTO | GOSSIP processReceivedGossipingRequests() : room key request from other user")
-        val senderKey = body.senderKey ?: return Unit
-                .also { Timber.w("missing senderKey") }
-                .also { cryptoStore.updateGossipingRequestState(request, GossipingRequestState.REJECTED) }
-        val sessionId = body.sessionId ?: return Unit
-                .also { Timber.w("missing sessionId") }
-                .also { cryptoStore.updateGossipingRequestState(request, GossipingRequestState.REJECTED) }
-
-        if (alg != MXCRYPTO_ALGORITHM_MEGOLM) {
-            return Unit
-                    .also { Timber.w("Only megolm is accepted here") }
-                    .also { cryptoStore.updateGossipingRequestState(request, GossipingRequestState.REJECTED) }
-        }
-
-        val roomEncryptor = roomEncryptorsStore.get(roomId) ?: return Unit
-                .also { Timber.w("no room Encryptor") }
-                .also { cryptoStore.updateGossipingRequestState(request, GossipingRequestState.REJECTED) }
-
-        cryptoCoroutineScope.launch(coroutineDispatchers.crypto) {
-            if (roomEncryptor is IMXGroupEncryption) {
-                val isSuccess = roomEncryptor.reshareKey(sessionId, userId, deviceId, senderKey)
-
-                if (isSuccess) {
-                    cryptoStore.updateGossipingRequestState(request, GossipingRequestState.ACCEPTED)
-                } else {
-                    cryptoStore.updateGossipingRequestState(request, GossipingRequestState.UNABLE_TO_PROCESS)
-                }
-            } else {
-                Timber.e("## CRYPTO | handleKeyRequestFromOtherUser() from:$userId: Unable to handle IMXGroupEncryption.reshareKey for $alg")
-            }
-        }
-        cryptoStore.updateGossipingRequestState(request, GossipingRequestState.RE_REQUESTED)
-    }
-
-    private fun processIncomingSecretShareRequest(request: IncomingSecretShareRequest) {
-        val secretName = request.secretName ?: return Unit.also {
-            cryptoStore.updateGossipingRequestState(request, GossipingRequestState.REJECTED)
-            Timber.v("## CRYPTO | GOSSIP processIncomingSecretShareRequest() : Missing secret name")
-        }
-
-        val userId = request.userId
-        if (userId == null || credentials.userId != userId) {
-            Timber.e("## CRYPTO | GOSSIP processIncomingSecretShareRequest() : Ignoring secret share request from other users")
-            cryptoStore.updateGossipingRequestState(request, GossipingRequestState.REJECTED)
-            return
-        }
-
-        val deviceId = request.deviceId
-                ?: return Unit.also {
-                    Timber.e("## CRYPTO | GOSSIP processIncomingSecretShareRequest() : Malformed request, no ")
-                    cryptoStore.updateGossipingRequestState(request, GossipingRequestState.REJECTED)
-                }
-
-        val device = cryptoStore.getUserDevice(userId, deviceId)
-                ?: return Unit.also {
-                    Timber.e("## CRYPTO | GOSSIP processIncomingSecretShareRequest() : Received secret share request from unknown device ${request.deviceId}")
-                    cryptoStore.updateGossipingRequestState(request, GossipingRequestState.REJECTED)
-                }
-
-        if (!device.isVerified || device.isBlocked) {
-            Timber.v("## CRYPTO | GOSSIP processIncomingSecretShareRequest() : Ignoring secret share request from untrusted/blocked session $device")
-            cryptoStore.updateGossipingRequestState(request, GossipingRequestState.REJECTED)
-            return
-        }
-
-        val isDeviceLocallyVerified = cryptoStore.getUserDevice(userId, deviceId)?.trustLevel?.isLocallyVerified()
-
-        when (secretName) {
-            MASTER_KEY_SSSS_NAME -> cryptoStore.getCrossSigningPrivateKeys()?.master
-            SELF_SIGNING_KEY_SSSS_NAME -> cryptoStore.getCrossSigningPrivateKeys()?.selfSigned
-            USER_SIGNING_KEY_SSSS_NAME -> cryptoStore.getCrossSigningPrivateKeys()?.user
-            KEYBACKUP_SECRET_SSSS_NAME -> cryptoStore.getKeyBackupRecoveryKeyInfo()?.recoveryKey
-                    ?.let {
-                        extractCurveKeyFromRecoveryKey(it)?.toBase64NoPadding()
-                    }
-            else                       -> null
-        }?.let { secretValue ->
-            Timber.i("## CRYPTO | GOSSIP processIncomingSecretShareRequest() : Sharing secret $secretName with $device locally trusted")
-            if (isDeviceLocallyVerified == true && hasBeenVerifiedLessThanFiveMinutesFromNow(deviceId)) {
-                val params = SendGossipWorker.Params(
-                        sessionId = sessionId,
-                        secretValue = secretValue,
-                        requestUserId = request.userId,
-                        requestDeviceId = request.deviceId,
-                        requestId = request.requestId,
-                        txnId = createUniqueTxnId()
-                )
-
-                cryptoStore.updateGossipingRequestState(request, GossipingRequestState.ACCEPTING)
-                val workRequest = gossipingWorkManager.createWork(WorkerParamsFactory.toData(params), true)
-                gossipingWorkManager.postWork(workRequest)
-            } else {
-                Timber.v("## CRYPTO | GOSSIP processIncomingSecretShareRequest() : Can't share secret $secretName with $device, verification too old")
-                cryptoStore.updateGossipingRequestState(request, GossipingRequestState.REJECTED)
-            }
-            return
-        }
-
-        Timber.v("## CRYPTO | GOSSIP processIncomingSecretShareRequest() : $secretName unknown at SDK level, asking to app layer")
-
-        request.ignore = Runnable {
-            cryptoStore.updateGossipingRequestState(request, GossipingRequestState.REJECTED)
-        }
-
-        request.share = { secretValue ->
-            val params = SendGossipWorker.Params(
-                    sessionId = userId,
-                    secretValue = secretValue,
-                    requestUserId = request.userId,
-                    requestDeviceId = request.deviceId,
-                    requestId = request.requestId,
-                    txnId = createUniqueTxnId()
-            )
-
-            cryptoStore.updateGossipingRequestState(request, GossipingRequestState.ACCEPTING)
-            val workRequest = gossipingWorkManager.createWork(WorkerParamsFactory.toData(params), true)
-            gossipingWorkManager.postWork(workRequest)
-            cryptoStore.updateGossipingRequestState(request, GossipingRequestState.ACCEPTED)
-        }
-
-        onShareRequest(request)
-    }
-
-    /**
-     * Dispatch onRoomKeyRequest
-     *
-     * @param request the request
-     */
-    private fun onRoomKeyRequest(request: IncomingRoomKeyRequest) {
-        synchronized(gossipingRequestListeners) {
-            for (listener in gossipingRequestListeners) {
-                try {
-                    listener.onRoomKeyRequest(request)
-                } catch (e: Exception) {
-                    Timber.e(e, "## CRYPTO | onRoomKeyRequest() failed")
-                }
-            }
-        }
-    }
-
-    /**
-     * Ask for a value to the listeners, and take the first one
-     */
-    private fun onShareRequest(request: IncomingSecretShareRequest) {
-        synchronized(gossipingRequestListeners) {
-            for (listener in gossipingRequestListeners) {
-                try {
-                    if (listener.onSecretShareRequest(request)) {
-                        return
-                    }
-                } catch (e: Exception) {
-                    Timber.e(e, "## CRYPTO | GOSSIP onRoomKeyRequest() failed")
-                }
-            }
-        }
-        // Not handled, ignore
-        request.ignore?.run()
-    }
-
-    /**
-     * A room key request cancellation has been received.
-     *
-     * @param request the cancellation request
-     */
-    private fun onRoomKeyRequestCancellation(request: IncomingRequestCancellation) {
-        synchronized(gossipingRequestListeners) {
-            for (listener in gossipingRequestListeners) {
-                try {
-                    listener.onRoomKeyRequestCancellation(request)
-                } catch (e: Exception) {
-                    Timber.e(e, "## CRYPTO | GOSSIP onRoomKeyRequestCancellation() failed")
-                }
-            }
-        }
-    }
-
-    fun addRoomKeysRequestListener(listener: GossipingRequestListener) {
-        synchronized(gossipingRequestListeners) {
-            gossipingRequestListeners.add(listener)
-        }
-    }
-
-    fun removeRoomKeysRequestListener(listener: GossipingRequestListener) {
-        synchronized(gossipingRequestListeners) {
-            gossipingRequestListeners.remove(listener)
-        }
-    }
-
-    companion object {
-        private const val FIVE_MINUTES_IN_MILLIS = 5 * 60 * 1000
-    }
-}
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/IncomingKeyRequestManager.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/IncomingKeyRequestManager.kt
new file mode 100644
index 0000000000..13f2fb861a
--- /dev/null
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/IncomingKeyRequestManager.kt
@@ -0,0 +1,463 @@
+/*
+ * Copyright 2022 The Matrix.org Foundation C.I.C.
+ *
+ * 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 org.matrix.android.sdk.internal.crypto
+
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.SupervisorJob
+import kotlinx.coroutines.asCoroutineDispatcher
+import kotlinx.coroutines.cancel
+import kotlinx.coroutines.launch
+import kotlinx.coroutines.sync.withLock
+import kotlinx.coroutines.withContext
+import org.matrix.android.sdk.api.MatrixCoroutineDispatchers
+import org.matrix.android.sdk.api.auth.data.Credentials
+import org.matrix.android.sdk.api.crypto.MXCRYPTO_ALGORITHM_MEGOLM
+import org.matrix.android.sdk.api.crypto.MXCryptoConfig
+import org.matrix.android.sdk.api.extensions.tryOrNull
+import org.matrix.android.sdk.api.logger.LoggerTag
+import org.matrix.android.sdk.api.session.crypto.keyshare.GossipingRequestListener
+import org.matrix.android.sdk.api.session.crypto.model.CryptoDeviceInfo
+import org.matrix.android.sdk.api.session.crypto.model.IncomingRoomKeyRequest
+import org.matrix.android.sdk.api.session.crypto.model.MXUsersDevicesMap
+import org.matrix.android.sdk.api.session.crypto.model.RoomKeyRequestBody
+import org.matrix.android.sdk.api.session.crypto.model.RoomKeyShareRequest
+import org.matrix.android.sdk.api.session.events.model.EventType
+import org.matrix.android.sdk.api.session.events.model.content.RoomKeyWithHeldContent
+import org.matrix.android.sdk.api.session.events.model.content.WithHeldCode
+import org.matrix.android.sdk.internal.crypto.actions.EnsureOlmSessionsForDevicesAction
+import org.matrix.android.sdk.internal.crypto.actions.MessageEncrypter
+import org.matrix.android.sdk.internal.crypto.store.IMXCryptoStore
+import org.matrix.android.sdk.internal.crypto.tasks.SendToDeviceTask
+import org.matrix.android.sdk.internal.session.SessionScope
+import org.matrix.android.sdk.internal.task.SemaphoreCoroutineSequencer
+import org.matrix.android.sdk.internal.util.time.Clock
+import timber.log.Timber
+import java.util.concurrent.Executors
+import javax.inject.Inject
+import kotlin.system.measureTimeMillis
+
+private val loggerTag = LoggerTag("IncomingKeyRequestManager", LoggerTag.CRYPTO)
+
+@SessionScope
+internal class IncomingKeyRequestManager @Inject constructor(
+        private val credentials: Credentials,
+        private val cryptoStore: IMXCryptoStore,
+        private val ensureOlmSessionsForDevicesAction: EnsureOlmSessionsForDevicesAction,
+        private val olmDevice: MXOlmDevice,
+        private val cryptoConfig: MXCryptoConfig,
+        private val messageEncrypter: MessageEncrypter,
+        private val coroutineDispatchers: MatrixCoroutineDispatchers,
+        private val sendToDeviceTask: SendToDeviceTask,
+        private val clock: Clock,
+) {
+
+    private val dispatcher = Executors.newSingleThreadExecutor().asCoroutineDispatcher()
+    private val outgoingRequestScope = CoroutineScope(SupervisorJob() + dispatcher)
+    val sequencer = SemaphoreCoroutineSequencer()
+
+    private val incomingRequestBuffer = mutableListOf()
+
+    // the listeners
+    private val gossipingRequestListeners: MutableSet = HashSet()
+
+    enum class MegolmRequestAction {
+        Request, Cancel
+    }
+
+    data class ValidMegolmRequestBody(
+            val requestId: String,
+            val requestingUserId: String,
+            val requestingDeviceId: String,
+            val roomId: String,
+            val senderKey: String,
+            val sessionId: String,
+            val action: MegolmRequestAction
+    ) {
+        fun shortDbgString() = "Request from $requestingUserId|$requestingDeviceId for session $sessionId in room $roomId"
+    }
+
+    private fun RoomKeyShareRequest.toValidMegolmRequest(senderId: String): ValidMegolmRequestBody? {
+        val deviceId = requestingDeviceId ?: return null
+        val body = body ?: return null
+        val roomId = body.roomId ?: return null
+        val sessionId = body.sessionId ?: return null
+        val senderKey = body.senderKey ?: return null
+        val requestId = this.requestId ?: return null
+        if (body.algorithm != MXCRYPTO_ALGORITHM_MEGOLM) return null
+        val action = when (this.action) {
+            "request" -> MegolmRequestAction.Request
+            "request_cancellation" -> MegolmRequestAction.Cancel
+            else -> null
+        } ?: return null
+        return ValidMegolmRequestBody(
+                requestId = requestId,
+                requestingUserId = senderId,
+                requestingDeviceId = deviceId,
+                roomId = roomId,
+                senderKey = senderKey,
+                sessionId = sessionId,
+                action = action
+        )
+    }
+
+    fun addNewIncomingRequest(senderId: String, request: RoomKeyShareRequest) {
+        if (!cryptoStore.isKeyGossipingEnabled()) {
+            Timber.tag(loggerTag.value)
+                    .i("Ignore incoming key request as per crypto config in room ${request.body?.roomId}")
+            return
+        }
+        outgoingRequestScope.launch {
+            // It is important to handle requests in order
+            sequencer.post {
+                val validMegolmRequest = request.toValidMegolmRequest(senderId) ?: return@post Unit.also {
+                    Timber.tag(loggerTag.value).w("Received key request for unknown algorithm ${request.body?.algorithm}")
+                }
+
+                // is there already one like that?
+                val existing = incomingRequestBuffer.firstOrNull { it == validMegolmRequest }
+                if (existing == null) {
+                    when (validMegolmRequest.action) {
+                        MegolmRequestAction.Request -> {
+                            // just add to the buffer
+                            incomingRequestBuffer.add(validMegolmRequest)
+                        }
+                        MegolmRequestAction.Cancel  -> {
+                            // ignore, we can't cancel as it's not known (probably already processed)
+                            // still notify app layer if it was passed up previously
+                            IncomingRoomKeyRequest.fromRestRequest(senderId, request, clock)?.let { iReq ->
+                                outgoingRequestScope.launch(coroutineDispatchers.computation) {
+                                    val listenersCopy = synchronized(gossipingRequestListeners) {
+                                        gossipingRequestListeners.toList()
+                                    }
+                                    listenersCopy.onEach {
+                                        tryOrNull {
+                                            withContext(coroutineDispatchers.main) {
+                                                it.onRequestCancelled(iReq)
+                                            }
+                                        }
+                                    }
+                                }
+                            }
+                        }
+                    }
+                } else {
+                    when (validMegolmRequest.action) {
+                        MegolmRequestAction.Request -> {
+                            // it's already in buffer, nop keep existing
+                        }
+                        MegolmRequestAction.Cancel  -> {
+                            // discard the request in buffer
+                            incomingRequestBuffer.remove(existing)
+                            outgoingRequestScope.launch(coroutineDispatchers.computation) {
+                                val listenersCopy = synchronized(gossipingRequestListeners) {
+                                    gossipingRequestListeners.toList()
+                                }
+                                listenersCopy.onEach {
+                                    IncomingRoomKeyRequest.fromRestRequest(senderId, request, clock)?.let { iReq ->
+                                        withContext(coroutineDispatchers.main) {
+                                            tryOrNull { it.onRequestCancelled(iReq) }
+                                        }
+                                    }
+                                }
+                            }
+                        }
+                    }
+                }
+            }
+        }
+    }
+
+    fun processIncomingRequests() {
+        outgoingRequestScope.launch {
+            sequencer.post {
+                measureTimeMillis {
+                    Timber.tag(loggerTag.value).v("processIncomingKeyRequests : ${incomingRequestBuffer.size} request to process")
+                    incomingRequestBuffer.forEach {
+                        // should not happen, we only store requests
+                        if (it.action != MegolmRequestAction.Request) return@forEach
+                        try {
+                            handleIncomingRequest(it)
+                        } catch (failure: Throwable) {
+                            // ignore and continue, should not happen
+                            Timber.tag(loggerTag.value).w(failure, "processIncomingKeyRequests : failed to process request $it")
+                        }
+                    }
+                    incomingRequestBuffer.clear()
+                }.let { duration ->
+                    Timber.tag(loggerTag.value).v("Finish processing incoming key request in $duration ms")
+                }
+            }
+        }
+    }
+
+    private suspend fun handleIncomingRequest(request: ValidMegolmRequestBody) {
+        // We don't want to download keys, if we don't know the device yet we won't share any how?
+        val requestingDevice =
+                cryptoStore.getUserDevice(request.requestingUserId, request.requestingDeviceId)
+                        ?: return Unit.also {
+                            Timber.tag(loggerTag.value).d("Ignoring key request: ${request.shortDbgString()}")
+                        }
+
+        cryptoStore.saveIncomingKeyRequestAuditTrail(
+                request.requestId,
+                request.roomId,
+                request.sessionId,
+                request.senderKey,
+                MXCRYPTO_ALGORITHM_MEGOLM,
+                request.requestingUserId,
+                request.requestingDeviceId
+        )
+
+        val roomAlgorithm = // withContext(coroutineDispatchers.crypto) {
+                cryptoStore.getRoomAlgorithm(request.roomId)
+//        }
+        if (roomAlgorithm != MXCRYPTO_ALGORITHM_MEGOLM) {
+            // strange we received a request for a room that is not encrypted
+            // maybe a broken state?
+            Timber.tag(loggerTag.value).w("Received a key request in a room with unsupported alg:$roomAlgorithm , req:${request.shortDbgString()}")
+            return
+        }
+
+        // Is it for one of our sessions?
+        if (request.requestingUserId == credentials.userId) {
+            Timber.tag(loggerTag.value).v("handling request from own user: megolm session ${request.sessionId}")
+
+            if (request.requestingDeviceId == credentials.deviceId) {
+                // ignore it's a remote echo
+                return
+            }
+            // If it's verified we share from the early index we know
+            // if not we check if it was originaly shared or not
+            if (requestingDevice.isVerified) {
+                // we share from the earliest known chain index
+                shareMegolmKey(request, requestingDevice, null)
+            } else {
+                shareIfItWasPreviouslyShared(request, requestingDevice)
+            }
+        } else {
+            if (cryptoConfig.limitRoomKeyRequestsToMyDevices) {
+                Timber.tag(loggerTag.value).v("Ignore request from other user as per crypto config: ${request.shortDbgString()}")
+                return
+            }
+            Timber.tag(loggerTag.value).v("handling request from other user: megolm session ${request.sessionId}")
+            if (requestingDevice.isBlocked) {
+                // it's blocked, so send a withheld code
+                sendWithheldForRequest(request, WithHeldCode.BLACKLISTED)
+            } else {
+                shareIfItWasPreviouslyShared(request, requestingDevice)
+            }
+        }
+    }
+
+    private suspend fun shareIfItWasPreviouslyShared(request: ValidMegolmRequestBody, requestingDevice: CryptoDeviceInfo) {
+        // we don't reshare unless it was previously shared with
+        val wasSessionSharedWithUser = withContext(coroutineDispatchers.crypto) {
+            cryptoStore.getSharedSessionInfo(request.roomId, request.sessionId, requestingDevice)
+        }
+        if (wasSessionSharedWithUser.found && wasSessionSharedWithUser.chainIndex != null) {
+            // we share from the index it was previously shared with
+            shareMegolmKey(request, requestingDevice, wasSessionSharedWithUser.chainIndex.toLong())
+        } else {
+            val isOwnDevice = requestingDevice.userId == credentials.userId
+            sendWithheldForRequest(request, if (isOwnDevice) WithHeldCode.UNVERIFIED else WithHeldCode.UNAUTHORISED)
+            // if it's our device we could delegate to the app layer to decide
+            if (isOwnDevice) {
+                outgoingRequestScope.launch(coroutineDispatchers.computation) {
+                    val listenersCopy = synchronized(gossipingRequestListeners) {
+                        gossipingRequestListeners.toList()
+                    }
+                    val iReq = IncomingRoomKeyRequest(
+                            userId = requestingDevice.userId,
+                            deviceId = requestingDevice.deviceId,
+                            requestId = request.requestId,
+                            requestBody = RoomKeyRequestBody(
+                                    algorithm = MXCRYPTO_ALGORITHM_MEGOLM,
+                                    senderKey = request.senderKey,
+                                    sessionId = request.sessionId,
+                                    roomId = request.roomId
+                            ),
+                            localCreationTimestamp = clock.epochMillis()
+                    )
+                    listenersCopy.onEach {
+                        withContext(coroutineDispatchers.main) {
+                            tryOrNull { it.onRoomKeyRequest(iReq) }
+                        }
+                    }
+                }
+            }
+        }
+    }
+
+    private suspend fun sendWithheldForRequest(request: ValidMegolmRequestBody, code: WithHeldCode) {
+        Timber.tag(loggerTag.value)
+                .w("Send withheld $code for req: ${request.shortDbgString()}")
+        val withHeldContent = RoomKeyWithHeldContent(
+                roomId = request.roomId,
+                senderKey = request.senderKey,
+                algorithm = MXCRYPTO_ALGORITHM_MEGOLM,
+                sessionId = request.sessionId,
+                codeString = code.value,
+                fromDevice = credentials.deviceId
+        )
+
+        val params = SendToDeviceTask.Params(
+                EventType.ROOM_KEY_WITHHELD,
+                MXUsersDevicesMap().apply {
+                    setObject(request.requestingUserId, request.requestingDeviceId, withHeldContent)
+                }
+        )
+        try {
+            withContext(coroutineDispatchers.io) {
+                sendToDeviceTask.execute(params)
+                Timber.tag(loggerTag.value)
+                        .d("Send withheld $code req: ${request.shortDbgString()}")
+            }
+
+            cryptoStore.saveWithheldAuditTrail(
+                    roomId = request.roomId,
+                    sessionId = request.sessionId,
+                    senderKey = request.senderKey,
+                    algorithm = MXCRYPTO_ALGORITHM_MEGOLM,
+                    code = code,
+                    userId = request.requestingUserId,
+                    deviceId = request.requestingDeviceId
+            )
+        } catch (failure: Throwable) {
+            // Ignore it's not that important?
+            // do we want to fallback to a worker?
+            Timber.tag(loggerTag.value)
+                    .w("Failed to send withheld $code req: ${request.shortDbgString()} reason:${failure.localizedMessage}")
+        }
+    }
+
+    suspend fun manuallyAcceptRoomKeyRequest(request: IncomingRoomKeyRequest) {
+        request.requestId ?: return
+        request.deviceId ?: return
+        request.userId ?: return
+        request.requestBody?.roomId ?: return
+        request.requestBody.senderKey ?: return
+        request.requestBody.sessionId ?: return
+        val validReq = ValidMegolmRequestBody(
+                requestId = request.requestId,
+                requestingDeviceId = request.deviceId,
+                requestingUserId = request.userId,
+                roomId = request.requestBody.roomId,
+                senderKey = request.requestBody.senderKey,
+                sessionId = request.requestBody.sessionId,
+                action = MegolmRequestAction.Request
+        )
+        val requestingDevice =
+                cryptoStore.getUserDevice(request.userId, request.deviceId)
+                        ?: return Unit.also {
+                            Timber.tag(loggerTag.value).d("Ignoring key request: ${validReq.shortDbgString()}")
+                        }
+
+        shareMegolmKey(validReq, requestingDevice, null)
+    }
+
+    private suspend fun shareMegolmKey(validRequest: ValidMegolmRequestBody,
+                                       requestingDevice: CryptoDeviceInfo,
+                                       chainIndex: Long?): Boolean {
+        Timber.tag(loggerTag.value)
+                .d("try to re-share Megolm Key at index $chainIndex for ${validRequest.shortDbgString()}")
+
+        val devicesByUser = mapOf(validRequest.requestingUserId to listOf(requestingDevice))
+        val usersDeviceMap = try {
+            ensureOlmSessionsForDevicesAction.handle(devicesByUser)
+        } catch (failure: Throwable) {
+            Timber.tag(loggerTag.value)
+                    .w("Failed to establish olm session")
+            sendWithheldForRequest(validRequest, WithHeldCode.NO_OLM)
+            return false
+        }
+
+        val olmSessionResult = usersDeviceMap.getObject(requestingDevice.userId, requestingDevice.deviceId)
+        if (olmSessionResult?.sessionId == null) {
+            Timber.tag(loggerTag.value)
+                    .w("reshareKey: no session with this device, probably because there were no one-time keys")
+            sendWithheldForRequest(validRequest, WithHeldCode.NO_OLM)
+            return false
+        }
+        val sessionHolder = try {
+            olmDevice.getInboundGroupSession(validRequest.sessionId, validRequest.senderKey, validRequest.roomId)
+        } catch (failure: Throwable) {
+            Timber.tag(loggerTag.value)
+                    .e(failure, "shareKeysWithDevice: failed to get session ${validRequest.requestingUserId}")
+            // It's unavailable
+            sendWithheldForRequest(validRequest, WithHeldCode.UNAVAILABLE)
+            return false
+        }
+
+        val export = sessionHolder.mutex.withLock {
+            sessionHolder.wrapper.exportKeys(chainIndex)
+        } ?: return false.also {
+            Timber.tag(loggerTag.value)
+                    .e("shareKeysWithDevice: failed to export group session ${validRequest.sessionId}")
+        }
+
+        val payloadJson = mapOf(
+                "type" to EventType.FORWARDED_ROOM_KEY,
+                "content" to export
+        )
+
+        val encodedPayload = messageEncrypter.encryptMessage(payloadJson, listOf(requestingDevice))
+        val sendToDeviceMap = MXUsersDevicesMap()
+        sendToDeviceMap.setObject(requestingDevice.userId, requestingDevice.deviceId, encodedPayload)
+        Timber.tag(loggerTag.value).d("reshareKey() : try sending session ${validRequest.sessionId} to ${requestingDevice.shortDebugString()}")
+        val sendToDeviceParams = SendToDeviceTask.Params(EventType.ENCRYPTED, sendToDeviceMap)
+        return try {
+            sendToDeviceTask.execute(sendToDeviceParams)
+            Timber.tag(loggerTag.value)
+                    .i("successfully re-shared session ${validRequest.sessionId} to ${requestingDevice.shortDebugString()}")
+            cryptoStore.saveForwardKeyAuditTrail(
+                    validRequest.roomId,
+                    validRequest.sessionId,
+                    validRequest.senderKey,
+                    MXCRYPTO_ALGORITHM_MEGOLM,
+                    requestingDevice.userId,
+                    requestingDevice.deviceId,
+                    chainIndex
+            )
+            true
+        } catch (failure: Throwable) {
+            Timber.tag(loggerTag.value)
+                    .e(failure, "fail to re-share session ${validRequest.sessionId} to ${requestingDevice.shortDebugString()}")
+            false
+        }
+    }
+
+    fun addRoomKeysRequestListener(listener: GossipingRequestListener) {
+        synchronized(gossipingRequestListeners) {
+            gossipingRequestListeners.add(listener)
+        }
+    }
+
+    fun removeRoomKeysRequestListener(listener: GossipingRequestListener) {
+        synchronized(gossipingRequestListeners) {
+            gossipingRequestListeners.remove(listener)
+        }
+    }
+
+    fun close() {
+        try {
+            outgoingRequestScope.cancel("User Terminate")
+            incomingRequestBuffer.clear()
+        } catch (failure: Throwable) {
+            Timber.tag(loggerTag.value).w("Failed to shutDown request manager")
+        }
+    }
+}
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/MXMegolmExportEncryption.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/MXMegolmExportEncryption.kt
index f8235bf344..e4a0f0376e 100755
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/MXMegolmExportEncryption.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/MXMegolmExportEncryption.kt
@@ -29,9 +29,10 @@ import javax.crypto.spec.SecretKeySpec
 import kotlin.experimental.and
 import kotlin.experimental.xor
 import kotlin.math.min
+import kotlin.system.measureTimeMillis
 
 /**
- * Utility class to import/export the crypto data
+ * Utility class to import/export the crypto data.
  */
 internal object MXMegolmExportEncryption {
     private const val HEADER_LINE = "-----BEGIN MEGOLM SESSION DATA-----"
@@ -65,7 +66,7 @@ internal object MXMegolmExportEncryption {
     }
 
     /**
-     * Decrypt a megolm key file
+     * Decrypt a megolm key file.
      *
      * @param data     the data to decrypt
      * @param password the password.
@@ -208,8 +209,8 @@ internal object MXMegolmExportEncryption {
     }
 
     /**
-     * Unbase64 an ascii-armoured megolm key file
-     * Strips the header and trailer lines, and unbase64s the content
+     * Unbase64 an ascii-armoured megolm key file.
+     * Strips the header and trailer lines, and unbase64s the content.
      *
      * @param data the input data
      * @return unbase64ed content
@@ -301,7 +302,7 @@ internal object MXMegolmExportEncryption {
     }
 
     /**
-     * Derive the AES and HMAC-SHA-256 keys for the file
+     * Derive the AES and HMAC-SHA-256 keys for the file.
      *
      * @param salt       salt for pbkdf
      * @param iterations number of pbkdf iterations
@@ -310,40 +311,40 @@ internal object MXMegolmExportEncryption {
      */
     @Throws(Exception::class)
     private fun deriveKeys(salt: ByteArray, iterations: Int, password: String): ByteArray {
-        val t0 = System.currentTimeMillis()
-
-        // based on https://en.wikipedia.org/wiki/PBKDF2 algorithm
-        // it is simpler than the generic algorithm because the expected key length is equal to the mac key length.
-        // noticed as dklen/hlen
-        val prf = Mac.getInstance("HmacSHA512")
-        prf.init(SecretKeySpec(password.toByteArray(Charsets.UTF_8), "HmacSHA512"))
-
-        // 512 bits key length
         val key = ByteArray(64)
-        val uc = ByteArray(64)
+        measureTimeMillis {
+            // based on https://en.wikipedia.org/wiki/PBKDF2 algorithm
+            // it is simpler than the generic algorithm because the expected key length is equal to the mac key length.
+            // noticed as dklen/hlen
+            val prf = Mac.getInstance("HmacSHA512")
+            prf.init(SecretKeySpec(password.toByteArray(Charsets.UTF_8), "HmacSHA512"))
 
-        // U1 = PRF(Password, Salt || INT_32_BE(i))
-        prf.update(salt)
-        val int32BE = ByteArray(4) { 0.toByte() }
-        int32BE[3] = 1.toByte()
-        prf.update(int32BE)
-        prf.doFinal(uc, 0)
+            // 512 bits key length
+            val uc = ByteArray(64)
 
-        // copy to the key
-        System.arraycopy(uc, 0, key, 0, uc.size)
-
-        for (index in 2..iterations) {
-            // Uc = PRF(Password, Uc-1)
-            prf.update(uc)
+            // U1 = PRF(Password, Salt || INT_32_BE(i))
+            prf.update(salt)
+            val int32BE = ByteArray(4) { 0.toByte() }
+            int32BE[3] = 1.toByte()
+            prf.update(int32BE)
             prf.doFinal(uc, 0)
 
-            // F(Password, Salt, c, i) = U1 ^ U2 ^ ... ^ Uc
-            for (byteIndex in uc.indices) {
-                key[byteIndex] = key[byteIndex] xor uc[byteIndex]
-            }
-        }
+            // copy to the key
+            System.arraycopy(uc, 0, key, 0, uc.size)
 
-        Timber.v("## deriveKeys() : $iterations in ${System.currentTimeMillis() - t0} ms")
+            for (index in 2..iterations) {
+                // Uc = PRF(Password, Uc-1)
+                prf.update(uc)
+                prf.doFinal(uc, 0)
+
+                // F(Password, Salt, c, i) = U1 ^ U2 ^ ... ^ Uc
+                for (byteIndex in uc.indices) {
+                    key[byteIndex] = key[byteIndex] xor uc[byteIndex]
+                }
+            }
+        }.also {
+            Timber.v("## deriveKeys() : $iterations in $it ms")
+        }
 
         return key
     }
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/MXOlmDevice.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/MXOlmDevice.kt
index 4947761f05..5620cbf769 100755
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/MXOlmDevice.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/MXOlmDevice.kt
@@ -35,6 +35,7 @@ import org.matrix.android.sdk.internal.session.SessionScope
 import org.matrix.android.sdk.internal.util.JsonCanonicalizer
 import org.matrix.android.sdk.internal.util.convertFromUTF8
 import org.matrix.android.sdk.internal.util.convertToUTF8
+import org.matrix.android.sdk.internal.util.time.Clock
 import org.matrix.olm.OlmAccount
 import org.matrix.olm.OlmException
 import org.matrix.olm.OlmMessage
@@ -42,7 +43,6 @@ import org.matrix.olm.OlmOutboundGroupSession
 import org.matrix.olm.OlmSession
 import org.matrix.olm.OlmUtility
 import timber.log.Timber
-import java.net.URLEncoder
 import javax.inject.Inject
 
 private val loggerTag = LoggerTag("MXOlmDevice", LoggerTag.CRYPTO)
@@ -55,7 +55,8 @@ internal class MXOlmDevice @Inject constructor(
          */
         private val store: IMXCryptoStore,
         private val olmSessionStore: OlmSessionStore,
-        private val inboundGroupSessionStore: InboundGroupSessionStore
+        private val inboundGroupSessionStore: InboundGroupSessionStore,
+        private val clock: Clock,
 ) {
 
     val mutex = Mutex()
@@ -147,9 +148,9 @@ internal class MXOlmDevice @Inject constructor(
     }
 
     /**
-     * Returns an unpublished fallback key
+     * Returns an unpublished fallback key.
      * A call to markKeysAsPublished will mark it as published and this
-     * call will return null (until a call to generateFallbackKey is made)
+     * call will return null (until a call to generateFallbackKey is made).
      */
     fun getFallbackKey(): MutableMap>? {
         try {
@@ -196,7 +197,7 @@ internal class MXOlmDevice @Inject constructor(
     }
 
     /**
-     * Release the instance
+     * Release the instance.
      */
     fun release() {
         olmUtility?.releaseUtility()
@@ -239,7 +240,7 @@ internal class MXOlmDevice @Inject constructor(
     }
 
     /**
-     * Generate some new one-time keys
+     * Generate some new one-time keys.
      *
      * @param numKeys number of keys to generate
      */
@@ -277,7 +278,7 @@ internal class MXOlmDevice @Inject constructor(
             // Pretend we've received a message at this point, otherwise
             // if we try to send a message to the device, it won't use
             // this session
-            olmSessionWrapper.onMessageReceived()
+            olmSessionWrapper.onMessageReceived(clock.epochMillis())
 
             olmSessionStore.storeSession(olmSessionWrapper, theirIdentityKey)
 
@@ -329,14 +330,6 @@ internal class MXOlmDevice @Inject constructor(
                 Timber.tag(loggerTag.value).e(e, "## createInboundSession() : removeOneTimeKeys failed")
             }
 
-            Timber.tag(loggerTag.value).v("## createInboundSession() : ciphertext: $ciphertext")
-            try {
-                val sha256 = olmUtility!!.sha256(URLEncoder.encode(ciphertext, "utf-8"))
-                Timber.tag(loggerTag.value).v("## createInboundSession() :ciphertext: SHA256: $sha256")
-            } catch (e: Exception) {
-                Timber.tag(loggerTag.value).e(e, "## createInboundSession() :ciphertext: cannot encode ciphertext")
-            }
-
             val olmMessage = OlmMessage()
             olmMessage.mCipherText = ciphertext
             olmMessage.mType = messageType.toLong()
@@ -348,7 +341,7 @@ internal class MXOlmDevice @Inject constructor(
 
                 val olmSessionWrapper = OlmSessionWrapper(olmSession, 0)
                 // This counts as a received message: set last received message time to now
-                olmSessionWrapper.onMessageReceived()
+                olmSessionWrapper.onMessageReceived(clock.epochMillis())
 
                 olmSessionStore.storeSession(olmSessionWrapper, theirDeviceIdentityKey)
             } catch (e: Exception) {
@@ -454,7 +447,7 @@ internal class MXOlmDevice @Inject constructor(
             payloadString =
                     olmSessionWrapper.mutex.withLock {
                         olmSessionWrapper.olmSession.decryptMessage(olmMessage).also {
-                            olmSessionWrapper.onMessageReceived()
+                            olmSessionWrapper.onMessageReceived(clock.epochMillis())
                         }
                     }
             olmSessionStore.storeSession(olmSessionWrapper, theirDeviceIdentityKey)
@@ -520,6 +513,7 @@ internal class MXOlmDevice @Inject constructor(
             return MXOutboundSessionInfo(
                     sessionId = sessionId,
                     sharedWithHelper = SharedWithHelper(roomId, sessionId, store),
+                    clock,
                     restoredOutboundGroupSession.creationTime
             )
         }
@@ -586,6 +580,13 @@ internal class MXOlmDevice @Inject constructor(
 
     //  Inbound group session
 
+    sealed interface AddSessionResult {
+        data class Imported(val ratchetIndex: Int) : AddSessionResult
+        abstract class Failure : AddSessionResult
+        object NotImported : Failure()
+        data class NotImportedHigherIndex(val newIndex: Int) : Failure()
+    }
+
     /**
      * Add an inbound group session to the session store.
      *
@@ -604,7 +605,7 @@ internal class MXOlmDevice @Inject constructor(
                                senderKey: String,
                                forwardingCurve25519KeyChain: List,
                                keysClaimed: Map,
-                               exportFormat: Boolean): Boolean {
+                               exportFormat: Boolean): AddSessionResult {
         val candidateSession = OlmInboundGroupSessionWrapper2(sessionKey, exportFormat)
         val existingSessionHolder = tryOrNull { getInboundGroupSession(sessionId, senderKey, roomId) }
         val existingSession = existingSessionHolder?.wrapper
@@ -612,7 +613,7 @@ internal class MXOlmDevice @Inject constructor(
         if (existingSession != null) {
             Timber.tag(loggerTag.value).d("## addInboundGroupSession() check if known session is better than candidate session")
             try {
-                val existingFirstKnown = existingSession.firstKnownIndex ?: return false.also {
+                val existingFirstKnown = existingSession.firstKnownIndex ?: return AddSessionResult.NotImported.also {
                     // This is quite unexpected, could throw if native was released?
                     Timber.tag(loggerTag.value).e("## addInboundGroupSession() null firstKnownIndex on existing session")
                     candidateSession.olmInboundGroupSession?.releaseSession()
@@ -623,12 +624,12 @@ internal class MXOlmDevice @Inject constructor(
                 if (newKnownFirstIndex != null && existingFirstKnown <= newKnownFirstIndex) {
                     Timber.tag(loggerTag.value).d("## addInboundGroupSession() : ignore session our is better $senderKey/$sessionId")
                     candidateSession.olmInboundGroupSession?.releaseSession()
-                    return false
+                    return AddSessionResult.NotImportedHigherIndex(newKnownFirstIndex.toInt())
                 }
             } catch (failure: Throwable) {
                 Timber.tag(loggerTag.value).e("## addInboundGroupSession() Failed to add inbound: ${failure.localizedMessage}")
                 candidateSession.olmInboundGroupSession?.releaseSession()
-                return false
+                return AddSessionResult.NotImported
             }
         }
 
@@ -638,19 +639,19 @@ internal class MXOlmDevice @Inject constructor(
         val candidateOlmInboundSession = candidateSession.olmInboundGroupSession
         if (null == candidateOlmInboundSession) {
             Timber.tag(loggerTag.value).e("## addInboundGroupSession : invalid session ")
-            return false
+            return AddSessionResult.NotImported
         }
 
         try {
             if (candidateOlmInboundSession.sessionIdentifier() != sessionId) {
                 Timber.tag(loggerTag.value).e("## addInboundGroupSession : ERROR: Mismatched group session ID from senderKey: $senderKey")
                 candidateOlmInboundSession.releaseSession()
-                return false
+                return AddSessionResult.NotImported
             }
         } catch (e: Throwable) {
             candidateOlmInboundSession.releaseSession()
             Timber.tag(loggerTag.value).e(e, "## addInboundGroupSession : sessionIdentifier() failed")
-            return false
+            return AddSessionResult.NotImported
         }
 
         candidateSession.senderKey = senderKey
@@ -664,7 +665,7 @@ internal class MXOlmDevice @Inject constructor(
             inboundGroupSessionStore.storeInBoundGroupSession(InboundGroupSessionHolder(candidateSession), sessionId, senderKey)
         }
 
-        return true
+        return AddSessionResult.Imported(candidateSession.firstKnownIndex?.toInt() ?: 0)
     }
 
     /**
@@ -787,7 +788,7 @@ internal class MXOlmDevice @Inject constructor(
 
                 if (timelineSet.contains(messageIndexKey)) {
                     val reason = String.format(MXCryptoError.DUPLICATE_MESSAGE_INDEX_REASON, decryptResult.mIndex)
-                    Timber.tag(loggerTag.value).e("## decryptGroupMessage() : $reason")
+                    Timber.tag(loggerTag.value).e("## decryptGroupMessage() timelineId=$timeline: $reason")
                     throw MXCryptoError.Base(MXCryptoError.ErrorType.DUPLICATED_MESSAGE_INDEX, reason)
                 }
 
@@ -855,7 +856,7 @@ internal class MXOlmDevice @Inject constructor(
     }
 
     /**
-     * Search an OlmSession
+     * Search an OlmSession.
      *
      * @param theirDeviceIdentityKey the device key
      * @param sessionId              the session Id
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/MyDeviceInfoHolder.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/MyDeviceInfoHolder.kt
index 9798d21576..3d09c0469b 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/MyDeviceInfoHolder.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/MyDeviceInfoHolder.kt
@@ -34,7 +34,7 @@ internal class MyDeviceInfoHolder @Inject constructor(
 ) {
     // Our device keys
     /**
-     * my device info
+     * my device info.
      */
     val myDevice: CryptoDeviceInfo
 
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/ObjectSigner.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/ObjectSigner.kt
index 68dd17324b..ab562d954a 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/ObjectSigner.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/ObjectSigner.kt
@@ -23,7 +23,7 @@ internal class ObjectSigner @Inject constructor(private val credentials: Credent
                                                 private val olmDevice: MXOlmDevice) {
 
     /**
-     * Sign Object
+     * Sign Object.
      *
      * Example:
      * 
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/OlmSessionStore.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/OlmSessionStore.kt
index f4fbca6a0f..fe280416ea 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/OlmSessionStore.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/OlmSessionStore.kt
@@ -26,12 +26,12 @@ import javax.inject.Inject
 private val loggerTag = LoggerTag("OlmSessionStore", LoggerTag.CRYPTO)
 
 /**
- * Keep the used olm session in memory and load them from the data layer when needed
- * Access is synchronized for thread safety
+ * Keep the used olm session in memory and load them from the data layer when needed.
+ * Access is synchronized for thread safety.
  */
 internal class OlmSessionStore @Inject constructor(private val store: IMXCryptoStore) {
     /**
-     * map of device key to list of olm sessions (it is possible to have several active sessions with a device)
+     * Map of device key to list of olm sessions (it is possible to have several active sessions with a device).
      */
     private val olmSessions = HashMap>()
 
@@ -66,7 +66,8 @@ internal class OlmSessionStore @Inject constructor(private val store: IMXCryptoS
         olmSessions.getOrPut(deviceKey) { mutableListOf() }.forEach { cached ->
             getSafeSessionIdentifier(cached.olmSession)?.let { cachedSessionId ->
                 if (!persistedKnownSessions.contains(cachedSessionId)) {
-                    persistedKnownSessions.add(cachedSessionId)
+                    // as it's in cache put in on top
+                    persistedKnownSessions.add(0, cachedSessionId)
                 }
             }
         }
@@ -88,7 +89,7 @@ internal class OlmSessionStore @Inject constructor(private val store: IMXCryptoS
     }
 
     /**
-     * Retrieve the last used sessionId, regarding `lastReceivedMessageTs`, or null if no session exist
+     * Retrieve the last used sessionId, regarding `lastReceivedMessageTs`, or null if no session exist.
      *
      * @param deviceKey the public key of the other device.
      * @return last used sessionId, or null if not found
@@ -109,7 +110,7 @@ internal class OlmSessionStore @Inject constructor(private val store: IMXCryptoS
     }
 
     /**
-     * Release all sessions and clear cache
+     * Release all sessions and clear cache.
      */
     @Synchronized
     fun clear() {
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/OneTimeKeysUploader.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/OneTimeKeysUploader.kt
index 792c9a25dc..8143e36892 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/OneTimeKeysUploader.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/OneTimeKeysUploader.kt
@@ -23,6 +23,7 @@ import org.matrix.android.sdk.internal.crypto.model.rest.KeysUploadResponse
 import org.matrix.android.sdk.internal.crypto.tasks.UploadKeysTask
 import org.matrix.android.sdk.internal.session.SessionScope
 import org.matrix.android.sdk.internal.util.JsonCanonicalizer
+import org.matrix.android.sdk.internal.util.time.Clock
 import org.matrix.olm.OlmAccount
 import timber.log.Timber
 import javax.inject.Inject
@@ -38,6 +39,7 @@ internal class OneTimeKeysUploader @Inject constructor(
         private val olmDevice: MXOlmDevice,
         private val objectSigner: ObjectSigner,
         private val uploadKeysTask: UploadKeysTask,
+        private val clock: Clock,
         context: Context
 ) {
     // tell if there is a OTK check in progress
@@ -77,7 +79,7 @@ internal class OneTimeKeysUploader @Inject constructor(
             Timber.v("maybeUploadOneTimeKeys: already in progress")
             return
         }
-        if (System.currentTimeMillis() - lastOneTimeKeyCheck < ONE_TIME_KEY_UPLOAD_PERIOD) {
+        if (clock.epochMillis() - lastOneTimeKeyCheck < ONE_TIME_KEY_UPLOAD_PERIOD) {
             // we've done a key upload recently.
             Timber.v("maybeUploadOneTimeKeys: executed too recently")
             return
@@ -94,7 +96,7 @@ internal class OneTimeKeysUploader @Inject constructor(
 
         Timber.d("maybeUploadOneTimeKeys: otk count $oneTimeKeyCountFromSync , unpublished fallback key ${olmDevice.hasUnpublishedFallbackKey()}")
 
-        lastOneTimeKeyCheck = System.currentTimeMillis()
+        lastOneTimeKeyCheck = clock.epochMillis()
 
         // We then check how many keys we can store in the Account object.
         val maxOneTimeKeys = olmDevice.getMaxNumberOfOneTimeKeys()
@@ -126,7 +128,7 @@ internal class OneTimeKeysUploader @Inject constructor(
 
         // Check if we need to forget a fallback key
         val latestPublishedTime = getLastFallbackKeyPublishTime()
-        if (latestPublishedTime != 0L && System.currentTimeMillis() - latestPublishedTime > FALLBACK_KEY_FORGET_DELAY) {
+        if (latestPublishedTime != 0L && clock.epochMillis() - latestPublishedTime > FALLBACK_KEY_FORGET_DELAY) {
             // This should be called once you are reasonably certain that you will not receive any more messages
             // that use the old fallback key
             Timber.d("## forgetFallbackKey()")
@@ -168,7 +170,7 @@ internal class OneTimeKeysUploader @Inject constructor(
         olmDevice.markKeysAsPublished()
         if (hadUnpublishedFallbackKey) {
             // It had an unpublished fallback key that was published just now
-            saveLastFallbackKeyPublishTime(System.currentTimeMillis())
+            saveLastFallbackKeyPublishTime(clock.epochMillis())
         }
 
         if (response.hasOneTimeKeyCountsForAlgorithm(MXKey.KEY_SIGNED_CURVE_25519_TYPE)) {
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/OutgoingGossipingRequestManager.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/OutgoingGossipingRequestManager.kt
deleted file mode 100755
index e6f6ac5053..0000000000
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/OutgoingGossipingRequestManager.kt
+++ /dev/null
@@ -1,167 +0,0 @@
-/*
- * Copyright 2020 The Matrix.org Foundation C.I.C.
- *
- * 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 org.matrix.android.sdk.internal.crypto
-
-import kotlinx.coroutines.CoroutineScope
-import kotlinx.coroutines.delay
-import kotlinx.coroutines.launch
-import org.matrix.android.sdk.api.MatrixCoroutineDispatchers
-import org.matrix.android.sdk.api.session.crypto.model.OutgoingGossipingRequestState
-import org.matrix.android.sdk.api.session.crypto.model.OutgoingRoomKeyRequest
-import org.matrix.android.sdk.api.session.crypto.model.RoomKeyRequestBody
-import org.matrix.android.sdk.internal.crypto.store.IMXCryptoStore
-import org.matrix.android.sdk.internal.crypto.tasks.createUniqueTxnId
-import org.matrix.android.sdk.internal.crypto.util.RequestIdHelper
-import org.matrix.android.sdk.internal.di.SessionId
-import org.matrix.android.sdk.internal.session.SessionScope
-import org.matrix.android.sdk.internal.worker.WorkerParamsFactory
-import timber.log.Timber
-import javax.inject.Inject
-
-@SessionScope
-internal class OutgoingGossipingRequestManager @Inject constructor(
-        @SessionId private val sessionId: String,
-        private val cryptoStore: IMXCryptoStore,
-        private val coroutineDispatchers: MatrixCoroutineDispatchers,
-        private val cryptoCoroutineScope: CoroutineScope,
-        private val gossipingWorkManager: GossipingWorkManager) {
-
-    /**
-     * Send off a room key request, if we haven't already done so.
-     *
-     *
-     * The `requestBody` is compared (with a deep-equality check) against
-     * previous queued or sent requests and if it matches, no change is made.
-     * Otherwise, a request is added to the pending list, and a job is started
-     * in the background to send it.
-     *
-     * @param requestBody requestBody
-     * @param recipients  recipients
-     */
-    fun sendRoomKeyRequest(requestBody: RoomKeyRequestBody, recipients: Map>) {
-        cryptoCoroutineScope.launch(coroutineDispatchers.crypto) {
-            cryptoStore.getOrAddOutgoingRoomKeyRequest(requestBody, recipients)?.let {
-                // Don't resend if it's already done, you need to cancel first (reRequest)
-                if (it.state == OutgoingGossipingRequestState.SENDING || it.state == OutgoingGossipingRequestState.SENT) {
-                    Timber.v("## CRYPTO - GOSSIP sendOutgoingRoomKeyRequest() : we already request for that session: $it")
-                    return@launch
-                }
-
-                sendOutgoingGossipingRequest(it)
-            }
-        }
-    }
-
-    fun sendSecretShareRequest(secretName: String, recipients: Map>) {
-        cryptoCoroutineScope.launch(coroutineDispatchers.crypto) {
-            // A bit dirty, but for better stability give other party some time to mark
-            // devices trusted :/
-            delay(1500)
-            cryptoStore.getOrAddOutgoingSecretShareRequest(secretName, recipients)?.let {
-                // TODO check if there is already one that is being sent?
-                if (it.state == OutgoingGossipingRequestState.SENDING
-                /**|| it.state == OutgoingGossipingRequestState.SENT*/
-                ) {
-                    Timber.v("## CRYPTO - GOSSIP sendSecretShareRequest() : we are already sending for that session: $it")
-                    return@launch
-                }
-
-                sendOutgoingGossipingRequest(it)
-            }
-        }
-    }
-
-    /**
-     * Cancel room key requests, if any match the given details
-     *
-     * @param requestBody requestBody
-     */
-    fun cancelRoomKeyRequest(requestBody: RoomKeyRequestBody) {
-        cryptoCoroutineScope.launch(coroutineDispatchers.computation) {
-            cancelRoomKeyRequest(requestBody, false)
-        }
-    }
-
-    /**
-     * Cancel room key requests, if any match the given details, and resend
-     *
-     * @param requestBody requestBody
-     */
-    fun resendRoomKeyRequest(requestBody: RoomKeyRequestBody) {
-        cryptoCoroutineScope.launch(coroutineDispatchers.computation) {
-            cancelRoomKeyRequest(requestBody, true)
-        }
-    }
-
-    /**
-     * Cancel room key requests, if any match the given details, and resend
-     *
-     * @param requestBody requestBody
-     * @param andResend   true to resend the key request
-     */
-    private fun cancelRoomKeyRequest(requestBody: RoomKeyRequestBody, andResend: Boolean) {
-        val req = cryptoStore.getOutgoingRoomKeyRequest(requestBody) // no request was made for this key
-                ?: return Unit.also {
-                    Timber.v("## CRYPTO - GOSSIP cancelRoomKeyRequest() Unknown request $requestBody")
-                }
-
-        sendOutgoingRoomKeyRequestCancellation(req, andResend)
-    }
-
-    /**
-     * Send the outgoing key request.
-     *
-     * @param request the request
-     */
-    private fun sendOutgoingGossipingRequest(request: OutgoingGossipingRequest) {
-        Timber.v("## CRYPTO - GOSSIP sendOutgoingGossipingRequest() : Requesting keys $request")
-
-        val params = SendGossipRequestWorker.Params(
-                sessionId = sessionId,
-                keyShareRequest = request as? OutgoingRoomKeyRequest,
-                secretShareRequest = request as? OutgoingSecretRequest,
-                txnId = createUniqueTxnId()
-        )
-        cryptoStore.updateOutgoingGossipingRequestState(request.requestId, OutgoingGossipingRequestState.SENDING)
-        val workRequest = gossipingWorkManager.createWork(WorkerParamsFactory.toData(params), true)
-        gossipingWorkManager.postWork(workRequest)
-    }
-
-    /**
-     * Given a OutgoingRoomKeyRequest, cancel it and delete the request record
-     *
-     * @param request the request
-     */
-    private fun sendOutgoingRoomKeyRequestCancellation(request: OutgoingRoomKeyRequest, resend: Boolean = false) {
-        Timber.v("## CRYPTO - sendOutgoingRoomKeyRequestCancellation $request")
-        val params = CancelGossipRequestWorker.Params.fromRequest(sessionId, request)
-        cryptoStore.updateOutgoingGossipingRequestState(request.requestId, OutgoingGossipingRequestState.CANCELLING)
-
-        val workRequest = gossipingWorkManager.createWork(WorkerParamsFactory.toData(params), true)
-        gossipingWorkManager.postWork(workRequest)
-
-        if (resend) {
-            val reSendParams = SendGossipRequestWorker.Params(
-                    sessionId = sessionId,
-                    keyShareRequest = request.copy(requestId = RequestIdHelper.createUniqueRequestId()),
-                    txnId = createUniqueTxnId()
-            )
-            val reSendWorkRequest = gossipingWorkManager.createWork(WorkerParamsFactory.toData(reSendParams), true)
-            gossipingWorkManager.postWork(reSendWorkRequest)
-        }
-    }
-}
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/OutgoingKeyRequestManager.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/OutgoingKeyRequestManager.kt
new file mode 100755
index 0000000000..d7652d0771
--- /dev/null
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/OutgoingKeyRequestManager.kt
@@ -0,0 +1,518 @@
+/*
+ * Copyright 2020 The Matrix.org Foundation C.I.C.
+ *
+ * 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 org.matrix.android.sdk.internal.crypto
+
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.SupervisorJob
+import kotlinx.coroutines.asCoroutineDispatcher
+import kotlinx.coroutines.cancel
+import kotlinx.coroutines.delay
+import kotlinx.coroutines.launch
+import kotlinx.coroutines.withContext
+import org.matrix.android.sdk.api.MatrixCoroutineDispatchers
+import org.matrix.android.sdk.api.crypto.MXCRYPTO_ALGORITHM_MEGOLM
+import org.matrix.android.sdk.api.crypto.MXCryptoConfig
+import org.matrix.android.sdk.api.extensions.tryOrNull
+import org.matrix.android.sdk.api.logger.LoggerTag
+import org.matrix.android.sdk.api.session.crypto.OutgoingKeyRequest
+import org.matrix.android.sdk.api.session.crypto.OutgoingRoomKeyRequestState
+import org.matrix.android.sdk.api.session.crypto.model.GossipingToDeviceObject
+import org.matrix.android.sdk.api.session.crypto.model.MXUsersDevicesMap
+import org.matrix.android.sdk.api.session.crypto.model.RoomKeyRequestBody
+import org.matrix.android.sdk.api.session.crypto.model.RoomKeyShareRequest
+import org.matrix.android.sdk.api.session.events.model.Event
+import org.matrix.android.sdk.api.session.events.model.EventType
+import org.matrix.android.sdk.api.session.events.model.content.EncryptedEventContent
+import org.matrix.android.sdk.api.session.events.model.content.RoomKeyWithHeldContent
+import org.matrix.android.sdk.api.session.events.model.toModel
+import org.matrix.android.sdk.api.util.fromBase64
+import org.matrix.android.sdk.internal.crypto.store.IMXCryptoStore
+import org.matrix.android.sdk.internal.crypto.tasks.SendToDeviceTask
+import org.matrix.android.sdk.internal.di.SessionId
+import org.matrix.android.sdk.internal.di.UserId
+import org.matrix.android.sdk.internal.session.SessionScope
+import org.matrix.android.sdk.internal.task.SemaphoreCoroutineSequencer
+import timber.log.Timber
+import java.util.Stack
+import java.util.concurrent.Executors
+import javax.inject.Inject
+import kotlin.system.measureTimeMillis
+
+private val loggerTag = LoggerTag("OutgoingKeyRequestManager", LoggerTag.CRYPTO)
+
+/**
+ * This class is responsible for sending key requests to other devices when a message failed to decrypt.
+ * It's lifecycle is based on the sync pulse:
+ *    - You can post queries for session, or report when you got a session
+ *    - At the end of the sync (onSyncComplete) it will then process all the posted request and send to devices
+ * If a request failed it will be retried at the end of the next sync
+ */
+@SessionScope
+internal class OutgoingKeyRequestManager @Inject constructor(
+        @SessionId private val sessionId: String,
+        @UserId private val myUserId: String,
+        private val cryptoStore: IMXCryptoStore,
+        private val coroutineDispatchers: MatrixCoroutineDispatchers,
+        private val cryptoConfig: MXCryptoConfig,
+        private val inboundGroupSessionStore: InboundGroupSessionStore,
+        private val sendToDeviceTask: SendToDeviceTask,
+        private val deviceListManager: DeviceListManager,
+        private val perSessionBackupQueryRateLimiter: PerSessionBackupQueryRateLimiter) {
+
+    private val dispatcher = Executors.newSingleThreadExecutor().asCoroutineDispatcher()
+    private val outgoingRequestScope = CoroutineScope(SupervisorJob() + dispatcher)
+    private val sequencer = SemaphoreCoroutineSequencer()
+
+    // We only have one active key request per session, so we don't request if it's already requested
+    // But it could make sense to check more the backup, as it's evolving.
+    // We keep a stack as we consider that the key requested last is more likely to be on screen?
+    private val requestDiscardedBecauseAlreadySentThatCouldBeTriedWithBackup = Stack>()
+
+    fun requestKeyForEvent(event: Event, force: Boolean) {
+        val (targets, body) = getRoomKeyRequestTargetForEvent(event) ?: return
+        val index = ratchetIndexForMessage(event) ?: 0
+        postRoomKeyRequest(body, targets, index, force)
+    }
+
+    private fun getRoomKeyRequestTargetForEvent(event: Event): Pair>, RoomKeyRequestBody>? {
+        val sender = event.senderId ?: return null
+        val encryptedEventContent = event.content.toModel() ?: return null.also {
+            Timber.tag(loggerTag.value).e("getRoomKeyRequestTargetForEvent Failed to re-request key, null content")
+        }
+        if (encryptedEventContent.algorithm != MXCRYPTO_ALGORITHM_MEGOLM) return null
+
+        val senderDevice = encryptedEventContent.deviceId
+        val recipients = if (cryptoConfig.limitRoomKeyRequestsToMyDevices) {
+            mapOf(
+                    myUserId to listOf("*")
+            )
+        } else {
+            if (event.senderId == myUserId) {
+                mapOf(
+                        myUserId to listOf("*")
+                )
+            } else {
+                // for the case where you share the key with a device that has a broken olm session
+                // The other user might Re-shares a megolm session key with devices if the key has already been
+                // sent to them.
+                mapOf(
+                        myUserId to listOf("*"),
+
+                        // We might not have deviceId in the future due to https://github.com/matrix-org/matrix-spec-proposals/pull/3700
+                        // so in this case query to all
+                        sender to listOf(senderDevice ?: "*")
+                )
+            }
+        }
+
+        val requestBody = RoomKeyRequestBody(
+                roomId = event.roomId,
+                algorithm = encryptedEventContent.algorithm,
+                senderKey = encryptedEventContent.senderKey,
+                sessionId = encryptedEventContent.sessionId
+        )
+        return recipients to requestBody
+    }
+
+    private fun ratchetIndexForMessage(event: Event): Int? {
+        val encryptedContent = event.content.toModel() ?: return null
+        if (encryptedContent.algorithm != MXCRYPTO_ALGORITHM_MEGOLM) return null
+        return encryptedContent.ciphertext?.fromBase64()?.inputStream()?.reader()?.let {
+            tryOrNull {
+                val megolmVersion = it.read()
+                if (megolmVersion != 3) return@tryOrNull null
+                /** Int tag */
+                if (it.read() != 8) return@tryOrNull null
+                it.read()
+            }
+        }
+    }
+
+    fun postRoomKeyRequest(requestBody: RoomKeyRequestBody, recipients: Map>, fromIndex: Int, force: Boolean = false) {
+        outgoingRequestScope.launch {
+            sequencer.post {
+                internalQueueRequest(requestBody, recipients, fromIndex, force)
+            }
+        }
+    }
+
+    /**
+     * Typically called when we the session as been imported or received meanwhile.
+     */
+    fun postCancelRequestForSessionIfNeeded(sessionId: String, roomId: String, senderKey: String, fromIndex: Int) {
+        outgoingRequestScope.launch {
+            sequencer.post {
+                internalQueueCancelRequest(sessionId, roomId, senderKey, fromIndex)
+            }
+        }
+    }
+
+    fun onSelfCrossSigningTrustChanged(newTrust: Boolean) {
+        if (newTrust) {
+            // we were previously not cross signed, but we are now
+            // so there is now more chances to get better replies for existing request
+            // Let's forget about sent request so that next time we try to decrypt we will resend requests
+            // We don't resend all because we don't want to generate a bulk of traffic
+            outgoingRequestScope.launch {
+                sequencer.post {
+                    cryptoStore.deleteOutgoingRoomKeyRequestInState(OutgoingRoomKeyRequestState.SENT)
+                }
+
+                sequencer.post {
+                    delay(1000)
+                    perSessionBackupQueryRateLimiter.refreshBackupInfoIfNeeded(true)
+                }
+            }
+        }
+    }
+
+    fun onRoomKeyForwarded(sessionId: String,
+                           algorithm: String,
+                           roomId: String,
+                           senderKey: String,
+                           fromDevice: String?,
+                           fromIndex: Int,
+                           event: Event) {
+        Timber.tag(loggerTag.value).d("Key forwarded for $sessionId from ${event.senderId}|$fromDevice at index $fromIndex")
+        outgoingRequestScope.launch {
+            sequencer.post {
+                cryptoStore.updateOutgoingRoomKeyReply(
+                        roomId = roomId,
+                        sessionId = sessionId,
+                        algorithm = algorithm,
+                        senderKey = senderKey,
+                        fromDevice = fromDevice,
+                        // strip out encrypted stuff as it's just a trail?
+                        event = event.copy(
+                                type = event.getClearType(),
+                                content = mapOf(
+                                        "chain_index" to fromIndex
+                                )
+                        )
+                )
+            }
+        }
+    }
+
+    fun onRoomKeyWithHeld(sessionId: String,
+                          algorithm: String,
+                          roomId: String,
+                          senderKey: String,
+                          fromDevice: String?,
+                          event: Event) {
+        outgoingRequestScope.launch {
+            sequencer.post {
+                Timber.tag(loggerTag.value).d("Withheld received for $sessionId from ${event.senderId}|$fromDevice")
+                Timber.tag(loggerTag.value).v("Withheld content ${event.getClearContent()}")
+
+                // We want to store withheld code from the sender of the message (owner of the megolm session), not from
+                // other devices that might gossip the key. If not the initial reason might be overridden
+                // by a request to one of our session.
+                event.getClearContent().toModel()?.let { withheld ->
+                    withContext(coroutineDispatchers.crypto) {
+                        tryOrNull {
+                            deviceListManager.downloadKeys(listOf(event.senderId ?: ""), false)
+                        }
+                        cryptoStore.getUserDeviceList(event.senderId ?: "")
+                                .also { devices ->
+                                    Timber.tag(loggerTag.value)
+                                            .v("Withheld Devices for ${event.senderId} are ${devices.orEmpty().joinToString { it.identityKey() ?: "" }}")
+                                }
+                                ?.firstOrNull {
+                                    it.identityKey() == senderKey
+                                }
+                    }.also {
+                        Timber.tag(loggerTag.value).v("Withheld device for sender key $senderKey is from ${it?.shortDebugString()}")
+                    }?.let {
+                        if (it.userId == event.senderId) {
+                            if (fromDevice != null) {
+                                if (it.deviceId == fromDevice) {
+                                    Timber.tag(loggerTag.value).v("Storing sender Withheld code ${withheld.code} for ${withheld.sessionId}")
+                                    cryptoStore.addWithHeldMegolmSession(withheld)
+                                }
+                            } else {
+                                Timber.tag(loggerTag.value).v("Storing sender Withheld code ${withheld.code} for ${withheld.sessionId}")
+                                cryptoStore.addWithHeldMegolmSession(withheld)
+                            }
+                        }
+                    }
+                }
+
+                // Here we store the replies from a given request
+                cryptoStore.updateOutgoingRoomKeyReply(
+                        roomId = roomId,
+                        sessionId = sessionId,
+                        algorithm = algorithm,
+                        senderKey = senderKey,
+                        fromDevice = fromDevice,
+                        event = event
+                )
+            }
+        }
+    }
+
+    /**
+     * Should be called after a sync, ideally if no catchup sync needed (as keys might arrive in those).
+     */
+    fun requireProcessAllPendingKeyRequests() {
+        outgoingRequestScope.launch {
+            sequencer.post {
+                internalProcessPendingKeyRequests()
+            }
+        }
+    }
+
+    private fun internalQueueCancelRequest(sessionId: String, roomId: String, senderKey: String, localKnownChainIndex: Int) {
+        // do we have known requests for that session??
+        Timber.tag(loggerTag.value).v("Cancel Key Request if needed for $sessionId")
+        val knownRequest = cryptoStore.getOutgoingRoomKeyRequest(
+                algorithm = MXCRYPTO_ALGORITHM_MEGOLM,
+                roomId = roomId,
+                sessionId = sessionId,
+                senderKey = senderKey
+        )
+        if (knownRequest.isEmpty()) return Unit.also {
+            Timber.tag(loggerTag.value).v("Handle Cancel Key Request for $sessionId -- Was not currently requested")
+        }
+        if (knownRequest.size > 1) {
+            // It's worth logging, there should be only one
+            Timber.tag(loggerTag.value).w("Found multiple requests for same sessionId $sessionId")
+        }
+        knownRequest.forEach { request ->
+            when (request.state) {
+                OutgoingRoomKeyRequestState.UNSENT                               -> {
+                    if (request.fromIndex >= localKnownChainIndex) {
+                        // we have a good index we can cancel
+                        cryptoStore.deleteOutgoingRoomKeyRequest(request.requestId)
+                    }
+                }
+                OutgoingRoomKeyRequestState.SENT                                 -> {
+                    // It was already sent, and index satisfied we can cancel
+                    if (request.fromIndex >= localKnownChainIndex) {
+                        cryptoStore.updateOutgoingRoomKeyRequestState(request.requestId, OutgoingRoomKeyRequestState.CANCELLATION_PENDING)
+                    }
+                }
+                OutgoingRoomKeyRequestState.CANCELLATION_PENDING                 -> {
+                    // It is already marked to be cancelled
+                }
+                OutgoingRoomKeyRequestState.CANCELLATION_PENDING_AND_WILL_RESEND -> {
+                    if (request.fromIndex >= localKnownChainIndex) {
+                        // we just want to cancel now
+                        cryptoStore.updateOutgoingRoomKeyRequestState(request.requestId, OutgoingRoomKeyRequestState.CANCELLATION_PENDING)
+                    }
+                }
+                OutgoingRoomKeyRequestState.SENT_THEN_CANCELED                   -> {
+                    // was already canceled
+                    // if we need a better index, should we resend?
+                }
+            }
+        }
+    }
+
+    fun close() {
+        try {
+            outgoingRequestScope.cancel("User Terminate")
+            requestDiscardedBecauseAlreadySentThatCouldBeTriedWithBackup.clear()
+        } catch (failure: Throwable) {
+            Timber.tag(loggerTag.value).w("Failed to shutDown request manager")
+        }
+    }
+
+    private fun internalQueueRequest(requestBody: RoomKeyRequestBody, recipients: Map>, fromIndex: Int, force: Boolean) {
+        if (!cryptoStore.isKeyGossipingEnabled()) {
+            // we might want to try backup?
+            if (requestBody.roomId != null && requestBody.sessionId != null) {
+                requestDiscardedBecauseAlreadySentThatCouldBeTriedWithBackup.push(requestBody.roomId to requestBody.sessionId)
+            }
+            Timber.tag(loggerTag.value).d("discarding request for ${requestBody.sessionId} as gossiping is disabled")
+            return
+        }
+
+        Timber.tag(loggerTag.value).d("Queueing key request for ${requestBody.sessionId} force:$force")
+        val existing = cryptoStore.getOutgoingRoomKeyRequest(requestBody)
+        Timber.tag(loggerTag.value).v("Queueing key request exiting is ${existing?.state}")
+        when (existing?.state) {
+            null                                                             -> {
+                // create a new one
+                cryptoStore.getOrAddOutgoingRoomKeyRequest(requestBody, recipients, fromIndex)
+            }
+            OutgoingRoomKeyRequestState.UNSENT                               -> {
+                // nothing it's new or not yet handled
+            }
+            OutgoingRoomKeyRequestState.SENT                                 -> {
+                // it was already requested
+                Timber.tag(loggerTag.value).d("The session ${requestBody.sessionId} is already requested")
+                if (force) {
+                    // update to UNSENT
+                    Timber.tag(loggerTag.value).d(".. force to request  ${requestBody.sessionId}")
+                    cryptoStore.updateOutgoingRoomKeyRequestState(existing.requestId, OutgoingRoomKeyRequestState.CANCELLATION_PENDING_AND_WILL_RESEND)
+                } else {
+                    if (existing.roomId != null && existing.sessionId != null) {
+                        requestDiscardedBecauseAlreadySentThatCouldBeTriedWithBackup.push(existing.roomId to existing.sessionId)
+                    }
+                }
+            }
+            OutgoingRoomKeyRequestState.CANCELLATION_PENDING                 -> {
+                // request is canceled only if I got the keys so what to do here...
+                if (force) {
+                    cryptoStore.updateOutgoingRoomKeyRequestState(existing.requestId, OutgoingRoomKeyRequestState.CANCELLATION_PENDING_AND_WILL_RESEND)
+                }
+            }
+            OutgoingRoomKeyRequestState.CANCELLATION_PENDING_AND_WILL_RESEND -> {
+                // It's already going to resend
+            }
+            OutgoingRoomKeyRequestState.SENT_THEN_CANCELED                   -> {
+                if (force) {
+                    cryptoStore.deleteOutgoingRoomKeyRequest(existing.requestId)
+                    cryptoStore.getOrAddOutgoingRoomKeyRequest(requestBody, recipients, fromIndex)
+                }
+            }
+        }
+
+        if (existing != null && existing.fromIndex >= fromIndex) {
+            // update the required index
+            cryptoStore.updateOutgoingRoomKeyRequiredIndex(existing.requestId, fromIndex)
+        }
+    }
+
+    private suspend fun internalProcessPendingKeyRequests() {
+        val toProcess = cryptoStore.getOutgoingRoomKeyRequests(OutgoingRoomKeyRequestState.pendingStates())
+        Timber.tag(loggerTag.value).v("Processing all pending key requests (found ${toProcess.size} pending)")
+
+        measureTimeMillis {
+            toProcess.forEach {
+                when (it.state) {
+                    OutgoingRoomKeyRequestState.UNSENT                               -> handleUnsentRequest(it)
+                    OutgoingRoomKeyRequestState.CANCELLATION_PENDING                 -> handleRequestToCancel(it)
+                    OutgoingRoomKeyRequestState.CANCELLATION_PENDING_AND_WILL_RESEND -> handleRequestToCancelWillResend(it)
+                    OutgoingRoomKeyRequestState.SENT_THEN_CANCELED,
+                    OutgoingRoomKeyRequestState.SENT                                 -> {
+                        // these are filtered out
+                    }
+                }
+            }
+        }.let {
+            Timber.tag(loggerTag.value).v("Finish processing pending key request in $it ms")
+        }
+
+        val maxBackupCallsBySync = 60
+        var currentCalls = 0
+        measureTimeMillis {
+            while (requestDiscardedBecauseAlreadySentThatCouldBeTriedWithBackup.isNotEmpty() && currentCalls < maxBackupCallsBySync) {
+                requestDiscardedBecauseAlreadySentThatCouldBeTriedWithBackup.pop().let { (roomId, sessionId) ->
+                    // we want to rate limit that somehow :/
+                    perSessionBackupQueryRateLimiter.tryFromBackupIfPossible(sessionId, roomId)
+                }
+                currentCalls++
+            }
+        }.let {
+            Timber.tag(loggerTag.value).v("Finish querying backup in $it ms")
+        }
+    }
+
+    private suspend fun handleUnsentRequest(request: OutgoingKeyRequest) {
+        // In order to avoid generating to_device traffic, we can first check if the key is backed up
+        Timber.tag(loggerTag.value).v("Handling unsent request for megolm session ${request.sessionId} in ${request.roomId}")
+        val sessionId = request.sessionId ?: return
+        val roomId = request.roomId ?: return
+        if (perSessionBackupQueryRateLimiter.tryFromBackupIfPossible(sessionId, roomId)) {
+            // let's see what's the index
+            val knownIndex = tryOrNull {
+                inboundGroupSessionStore.getInboundGroupSession(sessionId, request.requestBody?.senderKey ?: "")?.wrapper?.firstKnownIndex
+            }
+            if (knownIndex != null && knownIndex <= request.fromIndex) {
+                // we found the key in backup with good enough index, so we can just mark as cancelled, no need to send request
+                Timber.tag(loggerTag.value).v("Megolm session $sessionId successfully restored from backup, do not send request")
+                cryptoStore.deleteOutgoingRoomKeyRequest(request.requestId)
+                return
+            }
+        }
+
+        // we need to send the request
+        val toDeviceContent = RoomKeyShareRequest(
+                requestingDeviceId = cryptoStore.getDeviceId(),
+                requestId = request.requestId,
+                action = GossipingToDeviceObject.ACTION_SHARE_REQUEST,
+                body = request.requestBody
+        )
+        val contentMap = MXUsersDevicesMap()
+        request.recipients.forEach { userToDeviceMap ->
+            userToDeviceMap.value.forEach { deviceId ->
+                contentMap.setObject(userToDeviceMap.key, deviceId, toDeviceContent)
+            }
+        }
+
+        val params = SendToDeviceTask.Params(
+                eventType = EventType.ROOM_KEY_REQUEST,
+                contentMap = contentMap,
+                transactionId = request.requestId
+        )
+        try {
+            withContext(coroutineDispatchers.io) {
+                sendToDeviceTask.executeRetry(params, 3)
+            }
+            Timber.tag(loggerTag.value).d("Key request sent for $sessionId in room $roomId to ${request.recipients}")
+            // The request was sent, so update state
+            cryptoStore.updateOutgoingRoomKeyRequestState(request.requestId, OutgoingRoomKeyRequestState.SENT)
+            // TODO update the audit trail
+        } catch (failure: Throwable) {
+            Timber.tag(loggerTag.value).v("Failed to request $sessionId targets:${request.recipients}")
+        }
+    }
+
+    private suspend fun handleRequestToCancel(request: OutgoingKeyRequest): Boolean {
+        Timber.tag(loggerTag.value).v("handleRequestToCancel for megolm session ${request.sessionId}")
+        // we have to cancel this
+        val toDeviceContent = RoomKeyShareRequest(
+                requestingDeviceId = cryptoStore.getDeviceId(),
+                requestId = request.requestId,
+                action = GossipingToDeviceObject.ACTION_SHARE_CANCELLATION
+        )
+        val contentMap = MXUsersDevicesMap()
+        request.recipients.forEach { userToDeviceMap ->
+            userToDeviceMap.value.forEach { deviceId ->
+                contentMap.setObject(userToDeviceMap.key, deviceId, toDeviceContent)
+            }
+        }
+
+        val params = SendToDeviceTask.Params(
+                eventType = EventType.ROOM_KEY_REQUEST,
+                contentMap = contentMap,
+                transactionId = request.requestId
+        )
+        return try {
+            withContext(coroutineDispatchers.io) {
+                sendToDeviceTask.executeRetry(params, 3)
+            }
+            // The request cancellation was sent, we don't delete yet because we want
+            // to keep trace of the sent replies
+            cryptoStore.updateOutgoingRoomKeyRequestState(request.requestId, OutgoingRoomKeyRequestState.SENT_THEN_CANCELED)
+            true
+        } catch (failure: Throwable) {
+            Timber.tag(loggerTag.value).v("Failed to cancel request ${request.requestId} for session $sessionId targets:${request.recipients}")
+            false
+        }
+    }
+
+    private suspend fun handleRequestToCancelWillResend(request: OutgoingKeyRequest) {
+        if (handleRequestToCancel(request)) {
+            // this will create a new unsent request with no replies that will be process in the following call
+            cryptoStore.deleteOutgoingRoomKeyRequest(request.requestId)
+            request.requestBody?.let { cryptoStore.getOrAddOutgoingRoomKeyRequest(it, request.recipients, request.fromIndex) }
+        }
+    }
+}
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/OutgoingSecretRequest.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/OutgoingSecretRequest.kt
deleted file mode 100755
index 2ba2f5c817..0000000000
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/OutgoingSecretRequest.kt
+++ /dev/null
@@ -1,39 +0,0 @@
-/*
- * Copyright 2020 The Matrix.org Foundation C.I.C.
- *
- * 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 org.matrix.android.sdk.internal.crypto
-
-import com.squareup.moshi.JsonClass
-import org.matrix.android.sdk.api.session.crypto.model.OutgoingGossipingRequestState
-
-/**
- * Represents an outgoing room key request
- */
-@JsonClass(generateAdapter = true)
-internal class OutgoingSecretRequest(
-        // Secret Name
-        val secretName: String?,
-        // list of recipients for the request
-        override var recipients: Map>,
-        // Unique id for this request. Used for both
-        // an id within the request for later pairing with a cancellation, and for
-        // the transaction id when sending the to_device messages to our local
-        override var requestId: String,
-        // current state of this request
-        override var state: OutgoingGossipingRequestState) : OutgoingGossipingRequest {
-
-    // transaction id for the cancellation, if any
-}
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/PerSessionBackupQueryRateLimiter.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/PerSessionBackupQueryRateLimiter.kt
new file mode 100644
index 0000000000..5f62e7be9d
--- /dev/null
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/PerSessionBackupQueryRateLimiter.kt
@@ -0,0 +1,133 @@
+/*
+ * Copyright 2022 The Matrix.org Foundation C.I.C.
+ *
+ * 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 org.matrix.android.sdk.internal.crypto
+
+import dagger.Lazy
+import kotlinx.coroutines.withContext
+import org.matrix.android.sdk.api.MatrixCoroutineDispatchers
+import org.matrix.android.sdk.api.logger.LoggerTag
+import org.matrix.android.sdk.api.session.crypto.keysbackup.KeysVersionResult
+import org.matrix.android.sdk.api.session.crypto.keysbackup.SavedKeyBackupKeyInfo
+import org.matrix.android.sdk.api.session.crypto.model.ImportRoomKeysResult
+import org.matrix.android.sdk.api.util.awaitCallback
+import org.matrix.android.sdk.internal.crypto.keysbackup.DefaultKeysBackupService
+import org.matrix.android.sdk.internal.crypto.store.IMXCryptoStore
+import org.matrix.android.sdk.internal.util.time.Clock
+import timber.log.Timber
+import javax.inject.Inject
+
+// I keep the same name as OutgoingGossipingRequestManager to ease filtering of logs
+private val loggerTag = LoggerTag("OutgoingGossipingRequestManager", LoggerTag.CRYPTO)
+
+/**
+ * Used to try to get the key for UISI messages before sending room key request.
+ * We are adding some rate limiting to avoid querying too much for a key not in backup.
+ * Nonetheless the backup can be updated so we might want to retry from time to time.
+ */
+internal class PerSessionBackupQueryRateLimiter @Inject constructor(
+        private val coroutineDispatchers: MatrixCoroutineDispatchers,
+        private val keysBackupService: Lazy,
+        private val cryptoStore: IMXCryptoStore,
+        private val clock: Clock,
+) {
+
+    companion object {
+        const val MIN_TRY_BACKUP_PERIOD_MILLIS = 60 * 60_000 // 1 hour
+    }
+
+    data class Info(
+            val megolmSessionId: String,
+            val roomId: String
+    )
+
+    data class LastTry(
+            val backupVersion: String,
+            val timestamp: Long
+    )
+
+    /**
+     * Remember what we already tried (a key not in backup or some server issue).
+     * We might want to retry from time to time as the backup could have been updated.
+     */
+    private val lastFailureMap = mutableMapOf()
+
+    private var backupVersion: KeysVersionResult? = null
+    private var savedKeyBackupKeyInfo: SavedKeyBackupKeyInfo? = null
+    var backupWasCheckedFromServer: Boolean = false
+    val now = clock.epochMillis()
+
+    fun refreshBackupInfoIfNeeded(force: Boolean = false) {
+        if (backupWasCheckedFromServer && !force) return
+        Timber.tag(loggerTag.value).v("Checking if can access a backup")
+        backupWasCheckedFromServer = true
+        val knownBackupSecret = cryptoStore.getKeyBackupRecoveryKeyInfo()
+                ?: return Unit.also {
+                    Timber.tag(loggerTag.value).v("We don't have the backup secret!")
+                }
+        this.backupVersion = keysBackupService.get().keysBackupVersion
+        this.savedKeyBackupKeyInfo = knownBackupSecret
+    }
+
+    suspend fun tryFromBackupIfPossible(sessionId: String, roomId: String): Boolean {
+        Timber.tag(loggerTag.value).v("tryFromBackupIfPossible for session:$sessionId in $roomId")
+        refreshBackupInfoIfNeeded()
+        val currentVersion = backupVersion
+        if (savedKeyBackupKeyInfo?.version == null ||
+                currentVersion == null ||
+                currentVersion.version != savedKeyBackupKeyInfo?.version) {
+            // We can't access the backup
+            Timber.tag(loggerTag.value).v("Can't get backup version info")
+            return false
+        }
+        val cacheKey = Info(sessionId, roomId)
+        val lastTry = lastFailureMap[cacheKey]
+        val shouldQuery =
+                lastTry == null ||
+                        lastTry.backupVersion != currentVersion.version ||
+                        (now - lastTry.timestamp) > MIN_TRY_BACKUP_PERIOD_MILLIS
+
+        if (!shouldQuery) return false
+
+        val successfullyImported = withContext(coroutineDispatchers.io) {
+            try {
+                awaitCallback {
+                    keysBackupService.get().restoreKeysWithRecoveryKey(
+                            currentVersion,
+                            savedKeyBackupKeyInfo?.recoveryKey ?: "",
+                            roomId,
+                            sessionId,
+                            null,
+                            it
+                    )
+                }.successfullyNumberOfImportedKeys
+            } catch (failure: Throwable) {
+                // Fail silently
+                Timber.tag(loggerTag.value).v("getFromBackup failed ${failure.localizedMessage}")
+                0
+            }
+        }
+        if (successfullyImported == 1) {
+            Timber.tag(loggerTag.value).v("Found key in backup session:$sessionId in $roomId")
+            lastFailureMap.remove(cacheKey)
+            return true
+        } else {
+            Timber.tag(loggerTag.value).v("Failed to find key in backup session:$sessionId in $roomId")
+            lastFailureMap[cacheKey] = LastTry(currentVersion.version, clock.epochMillis())
+            return false
+        }
+    }
+}
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/RoomDecryptorProvider.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/RoomDecryptorProvider.kt
index dab806a565..c2f494b4b3 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/RoomDecryptorProvider.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/RoomDecryptorProvider.kt
@@ -79,7 +79,7 @@ internal class RoomDecryptorProvider @Inject constructor(
                             newSessionListeners.toList().forEach {
                                 try {
                                     it.onNewSession(roomId, senderKey, sessionId)
-                                } catch (e: Throwable) {
+                                } catch (ignore: Throwable) {
                                 }
                             }
                         }
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/RoomEncryptorsStore.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/RoomEncryptorsStore.kt
index ba97d96133..1a8c160d9c 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/RoomEncryptorsStore.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/RoomEncryptorsStore.kt
@@ -16,12 +16,21 @@
 
 package org.matrix.android.sdk.internal.crypto
 
+import org.matrix.android.sdk.api.crypto.MXCRYPTO_ALGORITHM_MEGOLM
+import org.matrix.android.sdk.api.crypto.MXCRYPTO_ALGORITHM_OLM
 import org.matrix.android.sdk.internal.crypto.algorithms.IMXEncrypting
+import org.matrix.android.sdk.internal.crypto.algorithms.megolm.MXMegolmEncryptionFactory
+import org.matrix.android.sdk.internal.crypto.algorithms.olm.MXOlmEncryptionFactory
+import org.matrix.android.sdk.internal.crypto.store.IMXCryptoStore
 import org.matrix.android.sdk.internal.session.SessionScope
 import javax.inject.Inject
 
 @SessionScope
-internal class RoomEncryptorsStore @Inject constructor() {
+internal class RoomEncryptorsStore @Inject constructor(
+        private val cryptoStore: IMXCryptoStore,
+        private val megolmEncryptionFactory: MXMegolmEncryptionFactory,
+        private val olmEncryptionFactory: MXOlmEncryptionFactory,
+) {
 
     // MXEncrypting instance for each room.
     private val roomEncryptors = mutableMapOf()
@@ -34,7 +43,18 @@ internal class RoomEncryptorsStore @Inject constructor() {
 
     fun get(roomId: String): IMXEncrypting? {
         return synchronized(roomEncryptors) {
-            roomEncryptors[roomId]
+            val cache = roomEncryptors[roomId]
+            if (cache != null) {
+                return@synchronized cache
+            } else {
+                val alg: IMXEncrypting? = when (cryptoStore.getRoomAlgorithm(roomId)) {
+                    MXCRYPTO_ALGORITHM_MEGOLM -> megolmEncryptionFactory.create(roomId)
+                    MXCRYPTO_ALGORITHM_OLM    -> olmEncryptionFactory.create(roomId)
+                    else                      -> null
+                }
+                alg?.let { roomEncryptors.put(roomId, it) }
+                return@synchronized alg
+            }
         }
     }
 }
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/SecretShareManager.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/SecretShareManager.kt
new file mode 100644
index 0000000000..6fb6914206
--- /dev/null
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/SecretShareManager.kt
@@ -0,0 +1,300 @@
+/*
+ * Copyright (c) 2022 The Matrix.org Foundation C.I.C.
+ *
+ * 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 org.matrix.android.sdk.internal.crypto
+
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.launch
+import kotlinx.coroutines.sync.Mutex
+import kotlinx.coroutines.sync.withLock
+import kotlinx.coroutines.withContext
+import org.matrix.android.sdk.api.MatrixCoroutineDispatchers
+import org.matrix.android.sdk.api.auth.data.Credentials
+import org.matrix.android.sdk.api.logger.LoggerTag
+import org.matrix.android.sdk.api.session.crypto.crosssigning.KEYBACKUP_SECRET_SSSS_NAME
+import org.matrix.android.sdk.api.session.crypto.crosssigning.MASTER_KEY_SSSS_NAME
+import org.matrix.android.sdk.api.session.crypto.crosssigning.SELF_SIGNING_KEY_SSSS_NAME
+import org.matrix.android.sdk.api.session.crypto.crosssigning.USER_SIGNING_KEY_SSSS_NAME
+import org.matrix.android.sdk.api.session.crypto.keysbackup.extractCurveKeyFromRecoveryKey
+import org.matrix.android.sdk.api.session.crypto.keyshare.GossipingRequestListener
+import org.matrix.android.sdk.api.session.crypto.model.MXUsersDevicesMap
+import org.matrix.android.sdk.api.session.crypto.model.SecretShareRequest
+import org.matrix.android.sdk.api.session.events.model.Event
+import org.matrix.android.sdk.api.session.events.model.EventType
+import org.matrix.android.sdk.api.session.events.model.content.SecretSendEventContent
+import org.matrix.android.sdk.api.session.events.model.toModel
+import org.matrix.android.sdk.api.util.toBase64NoPadding
+import org.matrix.android.sdk.internal.crypto.actions.EnsureOlmSessionsForDevicesAction
+import org.matrix.android.sdk.internal.crypto.actions.MessageEncrypter
+import org.matrix.android.sdk.internal.crypto.store.IMXCryptoStore
+import org.matrix.android.sdk.internal.crypto.tasks.SendToDeviceTask
+import org.matrix.android.sdk.internal.crypto.tasks.createUniqueTxnId
+import org.matrix.android.sdk.internal.session.SessionScope
+import org.matrix.android.sdk.internal.util.time.Clock
+import timber.log.Timber
+import javax.inject.Inject
+
+private val loggerTag = LoggerTag("SecretShareManager", LoggerTag.CRYPTO)
+
+@SessionScope
+internal class SecretShareManager @Inject constructor(
+        private val credentials: Credentials,
+        private val cryptoStore: IMXCryptoStore,
+        private val cryptoCoroutineScope: CoroutineScope,
+        private val messageEncrypter: MessageEncrypter,
+        private val ensureOlmSessionsForDevicesAction: EnsureOlmSessionsForDevicesAction,
+        private val sendToDeviceTask: SendToDeviceTask,
+        private val coroutineDispatchers: MatrixCoroutineDispatchers,
+        private val clock: Clock,
+) {
+
+    companion object {
+        private const val SECRET_SHARE_WINDOW_DURATION = 5 * 60 * 1000 // 5 minutes
+    }
+
+    /**
+     * Secret gossiping only occurs during a limited window period after interactive verification.
+     * We keep track of recent verification in memory for that purpose (no need to persist)
+     */
+    private val recentlyVerifiedDevices = mutableMapOf()
+    private val verifMutex = Mutex()
+
+    /**
+     * Secrets are exchanged as part of interactive verification,
+     * so we can just store in memory.
+     */
+    private val outgoingSecretRequests = mutableListOf()
+
+    // the listeners
+    private val gossipingRequestListeners: MutableSet = HashSet()
+
+    fun addListener(listener: GossipingRequestListener) {
+        synchronized(gossipingRequestListeners) {
+            gossipingRequestListeners.add(listener)
+        }
+    }
+
+    fun removeListener(listener: GossipingRequestListener) {
+        synchronized(gossipingRequestListeners) {
+            gossipingRequestListeners.remove(listener)
+        }
+    }
+
+    /**
+     * Called when a session has been verified.
+     * This information can be used by the manager to decide whether or not to fullfill gossiping requests.
+     * This should be called as fast as possible after a successful self interactive verification
+     */
+    fun onVerificationCompleteForDevice(deviceId: String) {
+        // For now we just keep an in memory cache
+        cryptoCoroutineScope.launch {
+            verifMutex.withLock {
+                recentlyVerifiedDevices[deviceId] = clock.epochMillis()
+            }
+        }
+    }
+
+    suspend fun handleSecretRequest(toDevice: Event) {
+        val request = toDevice.getClearContent().toModel()
+                ?: return Unit.also {
+                    Timber.tag(loggerTag.value)
+                            .w("handleSecretRequest() : malformed request")
+                }
+
+//            val (action, requestingDeviceId, requestId, secretName) = it
+        val secretName = request.secretName ?: return Unit.also {
+            Timber.tag(loggerTag.value)
+                    .v("handleSecretRequest() : Missing secret name")
+        }
+
+        val userId = toDevice.senderId ?: return Unit.also {
+            Timber.tag(loggerTag.value)
+                    .v("handleSecretRequest() : Missing senderId")
+        }
+
+        if (userId != credentials.userId) {
+            // secrets are only shared between our own devices
+            Timber.tag(loggerTag.value)
+                    .e("Ignoring secret share request from other users $userId")
+            return
+        }
+
+        val deviceId = request.requestingDeviceId
+                ?: return Unit.also {
+                    Timber.tag(loggerTag.value)
+                            .w("handleSecretRequest() : malformed request norequestingDeviceId ")
+                }
+
+        val device = cryptoStore.getUserDevice(credentials.userId, deviceId)
+                ?: return Unit.also {
+                    Timber.tag(loggerTag.value)
+                            .e("Received secret share request from unknown device $deviceId")
+                }
+
+        val isRequestingDeviceTrusted = device.isVerified
+        val isRecentInteractiveVerification = hasBeenVerifiedLessThanFiveMinutesFromNow(device.deviceId)
+        if (isRequestingDeviceTrusted && isRecentInteractiveVerification) {
+            // we can share the secret
+
+            val secretValue = when (secretName) {
+                MASTER_KEY_SSSS_NAME       -> cryptoStore.getCrossSigningPrivateKeys()?.master
+                SELF_SIGNING_KEY_SSSS_NAME -> cryptoStore.getCrossSigningPrivateKeys()?.selfSigned
+                USER_SIGNING_KEY_SSSS_NAME -> cryptoStore.getCrossSigningPrivateKeys()?.user
+                KEYBACKUP_SECRET_SSSS_NAME -> cryptoStore.getKeyBackupRecoveryKeyInfo()?.recoveryKey
+                        ?.let {
+                            extractCurveKeyFromRecoveryKey(it)?.toBase64NoPadding()
+                        }
+                else                       -> null
+            }
+            if (secretValue == null) {
+                Timber.tag(loggerTag.value)
+                        .i("The secret is unknown $secretName, passing to app layer")
+                val toList = synchronized(gossipingRequestListeners) { gossipingRequestListeners.toList() }
+                toList.onEach { listener ->
+                    listener.onSecretShareRequest(request)
+                }
+                return
+            }
+
+            val payloadJson = mapOf(
+                    "type" to EventType.SEND_SECRET,
+                    "content" to mapOf(
+                            "request_id" to request.requestId,
+                            "secret" to secretValue
+                    )
+            )
+
+            // Is it possible that we don't have an olm session?
+            val devicesByUser = mapOf(device.userId to listOf(device))
+            val usersDeviceMap = try {
+                ensureOlmSessionsForDevicesAction.handle(devicesByUser)
+            } catch (failure: Throwable) {
+                Timber.tag(loggerTag.value)
+                        .w("Can't share secret ${request.secretName}: Failed to establish olm session")
+                return
+            }
+
+            val olmSessionResult = usersDeviceMap.getObject(device.userId, device.deviceId)
+            if (olmSessionResult?.sessionId == null) {
+                Timber.tag(loggerTag.value)
+                        .w("secret share: no session with this device $deviceId, probably because there were no one-time keys")
+                return
+            }
+
+            val encodedPayload = messageEncrypter.encryptMessage(payloadJson, listOf(device))
+            val sendToDeviceMap = MXUsersDevicesMap()
+            sendToDeviceMap.setObject(device.userId, device.deviceId, encodedPayload)
+            val sendToDeviceParams = SendToDeviceTask.Params(EventType.ENCRYPTED, sendToDeviceMap)
+            try {
+                // raise the retries for secret
+                sendToDeviceTask.executeRetry(sendToDeviceParams, 6)
+                Timber.tag(loggerTag.value)
+                        .i("successfully shared secret $secretName to ${device.shortDebugString()}")
+                // TODO add a trail for that in audit logs
+            } catch (failure: Throwable) {
+                Timber.tag(loggerTag.value)
+                        .e(failure, "failed to send shared secret $secretName to ${device.shortDebugString()}")
+            }
+        } else {
+            Timber.tag(loggerTag.value)
+                    .d(" Received secret share request from un-authorised device ${device.deviceId}")
+        }
+    }
+
+    private suspend fun hasBeenVerifiedLessThanFiveMinutesFromNow(deviceId: String): Boolean {
+        val verifTimestamp = verifMutex.withLock {
+            recentlyVerifiedDevices[deviceId]
+        } ?: return false
+
+        val age = clock.epochMillis() - verifTimestamp
+
+        return age < SECRET_SHARE_WINDOW_DURATION
+    }
+
+    suspend fun requestSecretTo(deviceId: String, secretName: String) {
+        val cryptoDeviceInfo = cryptoStore.getUserDevice(credentials.userId, deviceId) ?: return Unit.also {
+            Timber.tag(loggerTag.value)
+                    .d("Can't request secret for $secretName unknown device $deviceId")
+        }
+        val toDeviceContent = SecretShareRequest(
+                requestingDeviceId = credentials.deviceId,
+                secretName = secretName,
+                requestId = createUniqueTxnId()
+        )
+
+        verifMutex.withLock {
+            outgoingSecretRequests.add(toDeviceContent)
+        }
+
+        val contentMap = MXUsersDevicesMap()
+        contentMap.setObject(cryptoDeviceInfo.userId, cryptoDeviceInfo.deviceId, toDeviceContent)
+
+        val params = SendToDeviceTask.Params(
+                eventType = EventType.REQUEST_SECRET,
+                contentMap = contentMap
+        )
+        try {
+            withContext(coroutineDispatchers.io) {
+                sendToDeviceTask.executeRetry(params, 3)
+            }
+            Timber.tag(loggerTag.value)
+                    .d("Secret request sent for $secretName to ${cryptoDeviceInfo.shortDebugString()}")
+            // TODO update the audit trail
+        } catch (failure: Throwable) {
+            Timber.tag(loggerTag.value)
+                    .w("Failed to request secret $secretName to ${cryptoDeviceInfo.shortDebugString()}")
+        }
+    }
+
+    suspend fun onSecretSendReceived(toDevice: Event, handleGossip: ((name: String, value: String) -> Boolean)) {
+        Timber.tag(loggerTag.value)
+                .i("onSecretSend() from ${toDevice.senderId} : onSecretSendReceived ${toDevice.content?.get("sender_key")}")
+        if (!toDevice.isEncrypted()) {
+            // secret send messages must be encrypted
+            Timber.tag(loggerTag.value).e("onSecretSend() :Received unencrypted secret send event")
+            return
+        }
+
+        // Was that sent by us?
+        if (toDevice.senderId != credentials.userId) {
+            Timber.tag(loggerTag.value).e("onSecretSend() : Ignore secret from other user ${toDevice.senderId}")
+            return
+        }
+
+        val secretContent = toDevice.getClearContent().toModel() ?: return
+
+        val existingRequest = verifMutex.withLock {
+            outgoingSecretRequests.firstOrNull { it.requestId == secretContent.requestId }
+        }
+
+        // As per spec:
+        // Clients should ignore m.secret.send events received from devices that it did not send an m.secret.request event to.
+        if (existingRequest?.secretName == null) {
+            Timber.tag(loggerTag.value).i("onSecretSend() : Ignore secret that was not requested: ${secretContent.requestId}")
+            return
+        }
+        // we don't need to cancel the request as we only request to one device
+        // just forget about the request now
+        verifMutex.withLock {
+            outgoingSecretRequests.remove(existingRequest)
+        }
+
+        if (!handleGossip(existingRequest.secretName, secretContent.secretValue)) {
+            // TODO Ask to application layer?
+            Timber.tag(loggerTag.value).v("onSecretSend() : secret not handled by SDK")
+        }
+    }
+}
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/SendGossipRequestWorker.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/SendGossipRequestWorker.kt
deleted file mode 100644
index dbdea97411..0000000000
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/SendGossipRequestWorker.kt
+++ /dev/null
@@ -1,151 +0,0 @@
-/*
- * Copyright 2020 The Matrix.org Foundation C.I.C.
- *
- * 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 org.matrix.android.sdk.internal.crypto
-
-import android.content.Context
-import androidx.work.WorkerParameters
-import com.squareup.moshi.JsonClass
-import org.matrix.android.sdk.api.auth.data.Credentials
-import org.matrix.android.sdk.api.failure.shouldBeRetried
-import org.matrix.android.sdk.api.session.crypto.model.GossipingToDeviceObject
-import org.matrix.android.sdk.api.session.crypto.model.MXUsersDevicesMap
-import org.matrix.android.sdk.api.session.crypto.model.OutgoingGossipingRequestState
-import org.matrix.android.sdk.api.session.crypto.model.OutgoingRoomKeyRequest
-import org.matrix.android.sdk.api.session.crypto.model.RoomKeyShareRequest
-import org.matrix.android.sdk.api.session.crypto.model.SecretShareRequest
-import org.matrix.android.sdk.api.session.events.model.Event
-import org.matrix.android.sdk.api.session.events.model.EventType
-import org.matrix.android.sdk.api.session.events.model.toContent
-import org.matrix.android.sdk.internal.SessionManager
-import org.matrix.android.sdk.internal.crypto.store.IMXCryptoStore
-import org.matrix.android.sdk.internal.crypto.tasks.SendToDeviceTask
-import org.matrix.android.sdk.internal.crypto.tasks.createUniqueTxnId
-import org.matrix.android.sdk.internal.session.SessionComponent
-import org.matrix.android.sdk.internal.worker.SessionSafeCoroutineWorker
-import org.matrix.android.sdk.internal.worker.SessionWorkerParams
-import timber.log.Timber
-import javax.inject.Inject
-
-internal class SendGossipRequestWorker(context: Context, params: WorkerParameters, sessionManager: SessionManager) :
-    SessionSafeCoroutineWorker(context, params, sessionManager, Params::class.java) {
-
-    @JsonClass(generateAdapter = true)
-    internal data class Params(
-            override val sessionId: String,
-            val keyShareRequest: OutgoingRoomKeyRequest? = null,
-            val secretShareRequest: OutgoingSecretRequest? = null,
-            // The txnId for the sendToDevice request. Nullable for compatibility reasons, but MUST always be provided
-            // to use the same value if this worker is retried.
-            val txnId: String? = null,
-            override val lastFailureMessage: String? = null
-    ) : SessionWorkerParams
-
-    @Inject lateinit var sendToDeviceTask: SendToDeviceTask
-    @Inject lateinit var cryptoStore: IMXCryptoStore
-    @Inject lateinit var credentials: Credentials
-
-    override fun injectWith(injector: SessionComponent) {
-        injector.inject(this)
-    }
-
-    override suspend fun doSafeWork(params: Params): Result {
-        // params.txnId should be provided in all cases now. But Params can be deserialized by
-        // the WorkManager from data serialized in a previous version of the application, so without the txnId field.
-        // So if not present, we create a txnId
-        val txnId = params.txnId ?: createUniqueTxnId()
-        val contentMap = MXUsersDevicesMap()
-        val eventType: String
-        val requestId: String
-        when {
-            params.keyShareRequest != null    -> {
-                eventType = EventType.ROOM_KEY_REQUEST
-                requestId = params.keyShareRequest.requestId
-                val toDeviceContent = RoomKeyShareRequest(
-                        requestingDeviceId = credentials.deviceId,
-                        requestId = params.keyShareRequest.requestId,
-                        action = GossipingToDeviceObject.ACTION_SHARE_REQUEST,
-                        body = params.keyShareRequest.requestBody
-                )
-                cryptoStore.saveGossipingEvent(Event(
-                        type = eventType,
-                        content = toDeviceContent.toContent(),
-                        senderId = credentials.userId
-                ).also {
-                    it.ageLocalTs = System.currentTimeMillis()
-                })
-
-                params.keyShareRequest.recipients.forEach { userToDeviceMap ->
-                    userToDeviceMap.value.forEach { deviceId ->
-                        contentMap.setObject(userToDeviceMap.key, deviceId, toDeviceContent)
-                    }
-                }
-            }
-            params.secretShareRequest != null -> {
-                eventType = EventType.REQUEST_SECRET
-                requestId = params.secretShareRequest.requestId
-                val toDeviceContent = SecretShareRequest(
-                        requestingDeviceId = credentials.deviceId,
-                        requestId = params.secretShareRequest.requestId,
-                        action = GossipingToDeviceObject.ACTION_SHARE_REQUEST,
-                        secretName = params.secretShareRequest.secretName
-                )
-
-                cryptoStore.saveGossipingEvent(Event(
-                        type = eventType,
-                        content = toDeviceContent.toContent(),
-                        senderId = credentials.userId
-                ).also {
-                    it.ageLocalTs = System.currentTimeMillis()
-                })
-
-                params.secretShareRequest.recipients.forEach { userToDeviceMap ->
-                    userToDeviceMap.value.forEach { deviceId ->
-                        contentMap.setObject(userToDeviceMap.key, deviceId, toDeviceContent)
-                    }
-                }
-            }
-            else                              -> {
-                return buildErrorResult(params, "Unknown empty gossiping request").also {
-                    Timber.e("Unknown empty gossiping request: $params")
-                }
-            }
-        }
-        try {
-            cryptoStore.updateOutgoingGossipingRequestState(requestId, OutgoingGossipingRequestState.SENDING)
-            sendToDeviceTask.execute(
-                    SendToDeviceTask.Params(
-                            eventType = eventType,
-                            contentMap = contentMap,
-                            transactionId = txnId
-                    )
-            )
-            cryptoStore.updateOutgoingGossipingRequestState(requestId, OutgoingGossipingRequestState.SENT)
-            return Result.success()
-        } catch (throwable: Throwable) {
-            return if (throwable.shouldBeRetried()) {
-                Result.retry()
-            } else {
-                cryptoStore.updateOutgoingGossipingRequestState(requestId, OutgoingGossipingRequestState.FAILED_TO_SEND)
-                buildErrorResult(params, throwable.localizedMessage ?: "error")
-            }
-        }
-    }
-
-    override fun buildErrorParams(params: Params, message: String): Params {
-        return params.copy(lastFailureMessage = params.lastFailureMessage ?: message)
-    }
-}
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/SendGossipWorker.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/SendGossipWorker.kt
deleted file mode 100644
index fd472fe73b..0000000000
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/SendGossipWorker.kt
+++ /dev/null
@@ -1,168 +0,0 @@
-/*
- * Copyright 2020 The Matrix.org Foundation C.I.C.
- *
- * 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 org.matrix.android.sdk.internal.crypto
-
-import android.content.Context
-import androidx.work.WorkerParameters
-import com.squareup.moshi.JsonClass
-import org.matrix.android.sdk.api.auth.data.Credentials
-import org.matrix.android.sdk.api.failure.shouldBeRetried
-import org.matrix.android.sdk.api.session.crypto.model.GossipingRequestState
-import org.matrix.android.sdk.api.session.crypto.model.MXUsersDevicesMap
-import org.matrix.android.sdk.api.session.events.model.Event
-import org.matrix.android.sdk.api.session.events.model.EventType
-import org.matrix.android.sdk.api.session.events.model.content.SecretSendEventContent
-import org.matrix.android.sdk.api.session.events.model.toContent
-import org.matrix.android.sdk.internal.SessionManager
-import org.matrix.android.sdk.internal.crypto.actions.EnsureOlmSessionsForDevicesAction
-import org.matrix.android.sdk.internal.crypto.actions.MessageEncrypter
-import org.matrix.android.sdk.internal.crypto.store.IMXCryptoStore
-import org.matrix.android.sdk.internal.crypto.tasks.SendToDeviceTask
-import org.matrix.android.sdk.internal.crypto.tasks.createUniqueTxnId
-import org.matrix.android.sdk.internal.session.SessionComponent
-import org.matrix.android.sdk.internal.worker.SessionSafeCoroutineWorker
-import org.matrix.android.sdk.internal.worker.SessionWorkerParams
-import timber.log.Timber
-import javax.inject.Inject
-
-internal class SendGossipWorker(
-        context: Context,
-        params: WorkerParameters,
-        sessionManager: SessionManager
-) : SessionSafeCoroutineWorker(context, params, sessionManager, Params::class.java) {
-
-    @JsonClass(generateAdapter = true)
-    internal data class Params(
-            override val sessionId: String,
-            val secretValue: String,
-            val requestUserId: String?,
-            val requestDeviceId: String?,
-            val requestId: String?,
-            // The txnId for the sendToDevice request. Nullable for compatibility reasons, but MUST always be provided
-            // to use the same value if this worker is retried.
-            val txnId: String? = null,
-            override val lastFailureMessage: String? = null
-    ) : SessionWorkerParams
-
-    @Inject lateinit var sendToDeviceTask: SendToDeviceTask
-    @Inject lateinit var cryptoStore: IMXCryptoStore
-    @Inject lateinit var credentials: Credentials
-    @Inject lateinit var messageEncrypter: MessageEncrypter
-    @Inject lateinit var ensureOlmSessionsForDevicesAction: EnsureOlmSessionsForDevicesAction
-
-    override fun injectWith(injector: SessionComponent) {
-        injector.inject(this)
-    }
-
-    override suspend fun doSafeWork(params: Params): Result {
-        // params.txnId should be provided in all cases now. But Params can be deserialized by
-        // the WorkManager from data serialized in a previous version of the application, so without the txnId field.
-        // So if not present, we create a txnId
-        val txnId = params.txnId ?: createUniqueTxnId()
-        val eventType: String = EventType.SEND_SECRET
-
-        val toDeviceContent = SecretSendEventContent(
-                requestId = params.requestId ?: "",
-                secretValue = params.secretValue
-        )
-
-        val requestingUserId = params.requestUserId ?: ""
-        val requestingDeviceId = params.requestDeviceId ?: ""
-        val deviceInfo = cryptoStore.getUserDevice(requestingUserId, requestingDeviceId)
-                ?: return buildErrorResult(params, "Unknown deviceInfo, cannot send message").also {
-                    cryptoStore.updateGossipingRequestState(
-                            requestUserId = params.requestUserId,
-                            requestDeviceId = params.requestDeviceId,
-                            requestId = params.requestId,
-                            state = GossipingRequestState.FAILED_TO_ACCEPTED
-                    )
-                    Timber.e("Unknown deviceInfo, cannot send message, sessionId: ${params.requestDeviceId}")
-                }
-
-        val sendToDeviceMap = MXUsersDevicesMap()
-
-        val devicesByUser = mapOf(requestingUserId to listOf(deviceInfo))
-        val usersDeviceMap = ensureOlmSessionsForDevicesAction.handle(devicesByUser)
-        val olmSessionResult = usersDeviceMap.getObject(requestingUserId, requestingDeviceId)
-        if (olmSessionResult?.sessionId == null) {
-            // no session with this device, probably because there
-            // were no one-time keys.
-            return buildErrorResult(params, "no session with this device").also {
-                cryptoStore.updateGossipingRequestState(
-                        requestUserId = params.requestUserId,
-                        requestDeviceId = params.requestDeviceId,
-                        requestId = params.requestId,
-                        state = GossipingRequestState.FAILED_TO_ACCEPTED
-                )
-                Timber.e("no session with this device $requestingDeviceId, probably because there were no one-time keys.")
-            }
-        }
-
-        val payloadJson = mapOf(
-                "type" to EventType.SEND_SECRET,
-                "content" to toDeviceContent.toContent()
-        )
-
-        try {
-            val encodedPayload = messageEncrypter.encryptMessage(payloadJson, listOf(deviceInfo))
-            sendToDeviceMap.setObject(requestingUserId, requestingDeviceId, encodedPayload)
-        } catch (failure: Throwable) {
-            Timber.e("## Fail to encrypt gossip + ${failure.localizedMessage}")
-        }
-
-        cryptoStore.saveGossipingEvent(Event(
-                type = eventType,
-                content = toDeviceContent.toContent(),
-                senderId = credentials.userId
-        ).also {
-            it.ageLocalTs = System.currentTimeMillis()
-        })
-
-        try {
-            sendToDeviceTask.execute(
-                    SendToDeviceTask.Params(
-                            eventType = EventType.ENCRYPTED,
-                            contentMap = sendToDeviceMap,
-                            transactionId = txnId
-                    )
-            )
-            cryptoStore.updateGossipingRequestState(
-                    requestUserId = params.requestUserId,
-                    requestDeviceId = params.requestDeviceId,
-                    requestId = params.requestId,
-                    state = GossipingRequestState.ACCEPTED
-            )
-            return Result.success()
-        } catch (throwable: Throwable) {
-            return if (throwable.shouldBeRetried()) {
-                Result.retry()
-            } else {
-                cryptoStore.updateGossipingRequestState(
-                        requestUserId = params.requestUserId,
-                        requestDeviceId = params.requestDeviceId,
-                        requestId = params.requestId,
-                        state = GossipingRequestState.FAILED_TO_ACCEPTED
-                )
-                buildErrorResult(params, throwable.localizedMessage ?: "error")
-            }
-        }
-    }
-
-    override fun buildErrorParams(params: Params, message: String): Params {
-        return params.copy(lastFailureMessage = params.lastFailureMessage ?: message)
-    }
-}
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/actions/EnsureOlmSessionsForDevicesAction.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/actions/EnsureOlmSessionsForDevicesAction.kt
index fffc2b4d4b..c728f1b682 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/actions/EnsureOlmSessionsForDevicesAction.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/actions/EnsureOlmSessionsForDevicesAction.kt
@@ -45,7 +45,7 @@ internal class EnsureOlmSessionsForDevicesAction @Inject constructor(
 
     /**
      * We want to synchronize a bit here, because we are iterating to check existing olm session and
-     * also adding some
+     * also adding some.
      */
     suspend fun handle(devicesByUser: Map>, force: Boolean = false): MXUsersDevicesMap {
         ensureMutex.withLock {
@@ -137,10 +137,14 @@ internal class EnsureOlmSessionsForDevicesAction @Inject constructor(
                 olmDevice.verifySignature(fingerprint, oneTimeKey.signalableJSONDictionary(), signature)
                 isVerified = true
             } catch (e: Exception) {
-                Timber.tag(loggerTag.value).d(e, "verifyKeyAndStartSession() : Verify error for otk: ${oneTimeKey.signalableJSONDictionary()}," +
-                        " signature:$signature fingerprint:$fingerprint")
-                Timber.tag(loggerTag.value).e("verifyKeyAndStartSession() : Verify error for ${deviceInfo.userId}|${deviceInfo.deviceId} " +
-                        " - signable json ${oneTimeKey.signalableJSONDictionary()}")
+                Timber.tag(loggerTag.value).d(
+                        e, "verifyKeyAndStartSession() : Verify error for otk: ${oneTimeKey.signalableJSONDictionary()}," +
+                        " signature:$signature fingerprint:$fingerprint"
+                )
+                Timber.tag(loggerTag.value).e(
+                        "verifyKeyAndStartSession() : Verify error for ${deviceInfo.userId}|${deviceInfo.deviceId} " +
+                                " - signable json ${oneTimeKey.signalableJSONDictionary()}"
+                )
                 errorMessage = e.message
             }
 
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/actions/MegolmSessionDataImporter.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/actions/MegolmSessionDataImporter.kt
index f9bcdf2c68..22c4e59b18 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/actions/MegolmSessionDataImporter.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/actions/MegolmSessionDataImporter.kt
@@ -17,22 +17,28 @@
 package org.matrix.android.sdk.internal.crypto.actions
 
 import androidx.annotation.WorkerThread
+import org.matrix.android.sdk.api.extensions.tryOrNull
 import org.matrix.android.sdk.api.listeners.ProgressListener
+import org.matrix.android.sdk.api.logger.LoggerTag
 import org.matrix.android.sdk.api.session.crypto.model.ImportRoomKeysResult
-import org.matrix.android.sdk.api.session.crypto.model.RoomKeyRequestBody
 import org.matrix.android.sdk.internal.crypto.MXOlmDevice
 import org.matrix.android.sdk.internal.crypto.MegolmSessionData
-import org.matrix.android.sdk.internal.crypto.OutgoingGossipingRequestManager
+import org.matrix.android.sdk.internal.crypto.OutgoingKeyRequestManager
 import org.matrix.android.sdk.internal.crypto.RoomDecryptorProvider
 import org.matrix.android.sdk.internal.crypto.algorithms.megolm.MXMegolmDecryption
 import org.matrix.android.sdk.internal.crypto.store.IMXCryptoStore
+import org.matrix.android.sdk.internal.util.time.Clock
 import timber.log.Timber
 import javax.inject.Inject
 
+private val loggerTag = LoggerTag("MegolmSessionDataImporter", LoggerTag.CRYPTO)
+
 internal class MegolmSessionDataImporter @Inject constructor(private val olmDevice: MXOlmDevice,
                                                              private val roomDecryptorProvider: RoomDecryptorProvider,
-                                                             private val outgoingGossipingRequestManager: OutgoingGossipingRequestManager,
-                                                             private val cryptoStore: IMXCryptoStore) {
+                                                             private val outgoingKeyRequestManager: OutgoingKeyRequestManager,
+                                                             private val cryptoStore: IMXCryptoStore,
+                                                             private val clock: Clock,
+) {
 
     /**
      * Import a list of megolm session keys.
@@ -47,7 +53,7 @@ internal class MegolmSessionDataImporter @Inject constructor(private val olmDevi
     fun handle(megolmSessionsData: List,
                fromBackup: Boolean,
                progressListener: ProgressListener?): ImportRoomKeysResult {
-        val t0 = System.currentTimeMillis()
+        val t0 = clock.epochMillis()
 
         val totalNumbersOfKeys = megolmSessionsData.size
         var lastProgress = 0
@@ -62,19 +68,23 @@ internal class MegolmSessionDataImporter @Inject constructor(private val olmDevi
             if (null != decrypting) {
                 try {
                     val sessionId = megolmSessionData.sessionId
-                    Timber.v("## importRoomKeys retrieve senderKey " + megolmSessionData.senderKey + " sessionId " + sessionId)
+                    Timber.tag(loggerTag.value).v("## importRoomKeys retrieve senderKey ${megolmSessionData.senderKey} sessionId $sessionId")
 
                     totalNumbersOfImportedKeys++
 
                     // cancel any outstanding room key requests for this session
-                    val roomKeyRequestBody = RoomKeyRequestBody(
-                            algorithm = megolmSessionData.algorithm,
-                            roomId = megolmSessionData.roomId,
-                            senderKey = megolmSessionData.senderKey,
-                            sessionId = megolmSessionData.sessionId
-                    )
 
-                    outgoingGossipingRequestManager.cancelRoomKeyRequest(roomKeyRequestBody)
+                    Timber.tag(loggerTag.value).d("Imported megolm session $sessionId from backup=$fromBackup in ${megolmSessionData.roomId}")
+                    outgoingKeyRequestManager.postCancelRequestForSessionIfNeeded(
+                            megolmSessionData.sessionId ?: "",
+                            megolmSessionData.roomId ?: "",
+                            megolmSessionData.senderKey ?: "",
+                            tryOrNull {
+                                olmInboundGroupSessionWrappers
+                                        .firstOrNull { it.olmInboundGroupSession?.sessionIdentifier() == megolmSessionData.sessionId }
+                                        ?.firstKnownIndex?.toInt()
+                            } ?: 0
+                    )
 
                     // Have another go at decrypting events sent with this session
                     when (decrypting) {
@@ -83,7 +93,7 @@ internal class MegolmSessionDataImporter @Inject constructor(private val olmDevi
                         }
                     }
                 } catch (e: Exception) {
-                    Timber.e(e, "## importRoomKeys() : onNewSession failed")
+                    Timber.tag(loggerTag.value).e(e, "## importRoomKeys() : onNewSession failed")
                 }
             }
 
@@ -103,9 +113,9 @@ internal class MegolmSessionDataImporter @Inject constructor(private val olmDevi
             cryptoStore.markBackupDoneForInboundGroupSessions(olmInboundGroupSessionWrappers)
         }
 
-        val t1 = System.currentTimeMillis()
+        val t1 = clock.epochMillis()
 
-        Timber.v("## importMegolmSessionsData : sessions import " + (t1 - t0) + " ms (" + megolmSessionsData.size + " sessions)")
+        Timber.tag(loggerTag.value).v("## importMegolmSessionsData : sessions import " + (t1 - t0) + " ms (" + megolmSessionsData.size + " sessions)")
 
         return ImportRoomKeysResult(totalNumbersOfKeys, totalNumbersOfImportedKeys)
     }
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/algorithms/IMXDecrypting.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/algorithms/IMXDecrypting.kt
index 2ea4e1dd29..34006ecfde 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/algorithms/IMXDecrypting.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/algorithms/IMXDecrypting.kt
@@ -17,19 +17,17 @@
 package org.matrix.android.sdk.internal.crypto.algorithms
 
 import org.matrix.android.sdk.api.session.crypto.MXCryptoError
-import org.matrix.android.sdk.api.session.crypto.model.IncomingRoomKeyRequest
-import org.matrix.android.sdk.api.session.crypto.model.IncomingSecretShareRequest
 import org.matrix.android.sdk.api.session.crypto.model.MXEventDecryptionResult
 import org.matrix.android.sdk.api.session.events.model.Event
 import org.matrix.android.sdk.internal.crypto.keysbackup.DefaultKeysBackupService
 
 /**
- * An interface for decrypting data
+ * An interface for decrypting data.
  */
 internal interface IMXDecrypting {
 
     /**
-     * Decrypt an event
+     * Decrypt an event.
      *
      * @param event    the raw event.
      * @param timeline the id of the timeline where the event is decrypted. It is used to prevent replay attack.
@@ -44,23 +42,4 @@ internal interface IMXDecrypting {
      * @param event the key event.
      */
     fun onRoomKeyEvent(event: Event, defaultKeysBackupService: DefaultKeysBackupService) {}
-
-    /**
-     * Determine if we have the keys necessary to respond to a room key request
-     *
-     * @param request keyRequest
-     * @return true if we have the keys and could (theoretically) share
-     */
-    fun hasKeysForKeyRequest(request: IncomingRoomKeyRequest): Boolean = false
-
-    /**
-     * Send the response to a room key request.
-     *
-     * @param request keyRequest
-     */
-    fun shareKeysWithDevice(request: IncomingRoomKeyRequest) {}
-
-    fun shareSecretWithDevice(request: IncomingSecretShareRequest, secretValue: String) {}
-
-    fun requestKeysForEvent(event: Event, withHeld: Boolean)
 }
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/algorithms/IMXEncrypting.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/algorithms/IMXEncrypting.kt
index 5294429198..1d84120208 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/algorithms/IMXEncrypting.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/algorithms/IMXEncrypting.kt
@@ -19,7 +19,7 @@ package org.matrix.android.sdk.internal.crypto.algorithms
 import org.matrix.android.sdk.api.session.events.model.Content
 
 /**
- * An interface for encrypting data
+ * An interface for encrypting data.
  */
 internal interface IMXEncrypting {
 
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/algorithms/megolm/MXMegolmDecryption.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/algorithms/megolm/MXMegolmDecryption.kt
index 4c407c9eb9..8321b73b75 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/algorithms/megolm/MXMegolmDecryption.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/algorithms/megolm/MXMegolmDecryption.kt
@@ -17,51 +17,32 @@
 package org.matrix.android.sdk.internal.crypto.algorithms.megolm
 
 import dagger.Lazy
-import kotlinx.coroutines.CoroutineScope
-import kotlinx.coroutines.launch
-import kotlinx.coroutines.sync.withLock
-import org.matrix.android.sdk.api.MatrixCoroutineDispatchers
 import org.matrix.android.sdk.api.logger.LoggerTag
 import org.matrix.android.sdk.api.session.crypto.MXCryptoError
 import org.matrix.android.sdk.api.session.crypto.NewSessionListener
 import org.matrix.android.sdk.api.session.crypto.model.ForwardedRoomKeyContent
-import org.matrix.android.sdk.api.session.crypto.model.IncomingRoomKeyRequest
 import org.matrix.android.sdk.api.session.crypto.model.MXEventDecryptionResult
-import org.matrix.android.sdk.api.session.crypto.model.MXUsersDevicesMap
-import org.matrix.android.sdk.api.session.crypto.model.RoomKeyRequestBody
 import org.matrix.android.sdk.api.session.events.model.Event
 import org.matrix.android.sdk.api.session.events.model.EventType
 import org.matrix.android.sdk.api.session.events.model.content.EncryptedEventContent
 import org.matrix.android.sdk.api.session.events.model.content.RoomKeyContent
-import org.matrix.android.sdk.api.session.events.model.content.RoomKeyWithHeldContent
 import org.matrix.android.sdk.api.session.events.model.toModel
-import org.matrix.android.sdk.internal.crypto.DeviceListManager
 import org.matrix.android.sdk.internal.crypto.MXOlmDevice
-import org.matrix.android.sdk.internal.crypto.OutgoingGossipingRequestManager
-import org.matrix.android.sdk.internal.crypto.actions.EnsureOlmSessionsForDevicesAction
-import org.matrix.android.sdk.internal.crypto.actions.MessageEncrypter
+import org.matrix.android.sdk.internal.crypto.OutgoingKeyRequestManager
 import org.matrix.android.sdk.internal.crypto.algorithms.IMXDecrypting
-import org.matrix.android.sdk.internal.crypto.algorithms.IMXWithHeldExtension
 import org.matrix.android.sdk.internal.crypto.keysbackup.DefaultKeysBackupService
 import org.matrix.android.sdk.internal.crypto.store.IMXCryptoStore
-import org.matrix.android.sdk.internal.crypto.tasks.SendToDeviceTask
 import org.matrix.android.sdk.internal.session.StreamEventsManager
 import timber.log.Timber
 
 private val loggerTag = LoggerTag("MXMegolmDecryption", LoggerTag.CRYPTO)
 
-internal class MXMegolmDecryption(private val userId: String,
-                                  private val olmDevice: MXOlmDevice,
-                                  private val deviceListManager: DeviceListManager,
-                                  private val outgoingGossipingRequestManager: OutgoingGossipingRequestManager,
-                                  private val messageEncrypter: MessageEncrypter,
-                                  private val ensureOlmSessionsForDevicesAction: EnsureOlmSessionsForDevicesAction,
-                                  private val cryptoStore: IMXCryptoStore,
-                                  private val sendToDeviceTask: SendToDeviceTask,
-                                  private val coroutineDispatchers: MatrixCoroutineDispatchers,
-                                  private val cryptoCoroutineScope: CoroutineScope,
-                                  private val liveEventManager: Lazy
-) : IMXDecrypting, IMXWithHeldExtension {
+internal class MXMegolmDecryption(
+        private val olmDevice: MXOlmDevice,
+        private val outgoingKeyRequestManager: OutgoingKeyRequestManager,
+        private val cryptoStore: IMXCryptoStore,
+        private val liveEventManager: Lazy
+) : IMXDecrypting {
 
     var newSessionListener: NewSessionListener? = null
 
@@ -73,10 +54,7 @@ internal class MXMegolmDecryption(private val userId: String,
 
     @Throws(MXCryptoError::class)
     override suspend fun decryptEvent(event: Event, timeline: String): MXEventDecryptionResult {
-        // If cross signing is enabled, we don't send request until the keys are trusted
-        // There could be a race effect here when xsigning is enabled, we should ensure that keys was downloaded once
-        val requestOnFail = cryptoStore.getMyCrossSigningInfo()?.isTrusted() == true
-        return decryptEvent(event, timeline, requestOnFail)
+        return decryptEvent(event, timeline, true)
     }
 
     @Throws(MXCryptoError::class)
@@ -96,11 +74,13 @@ internal class MXMegolmDecryption(private val userId: String,
         }
 
         return runCatching {
-            olmDevice.decryptGroupMessage(encryptedEventContent.ciphertext,
+            olmDevice.decryptGroupMessage(
+                    encryptedEventContent.ciphertext,
                     event.roomId,
                     timeline,
                     encryptedEventContent.sessionId,
-                    encryptedEventContent.senderKey)
+                    encryptedEventContent.senderKey
+            )
         }
                 .fold(
                         { olmDecryptionResult ->
@@ -124,27 +104,27 @@ internal class MXMegolmDecryption(private val userId: String,
                             if (throwable is MXCryptoError.OlmError) {
                                 // TODO Check the value of .message
                                 if (throwable.olmException.message == "UNKNOWN_MESSAGE_INDEX") {
-                                    // addEventToPendingList(event, timeline)
-                                    // The session might has been partially withheld (and only pass ratcheted)
-                                    val withHeldInfo = cryptoStore.getWithHeldMegolmSession(event.roomId, encryptedEventContent.sessionId)
-                                    if (withHeldInfo != null) {
-                                        if (requestKeysOnFail) {
-                                            requestKeysForEvent(event, true)
-                                        }
-                                        // Encapsulate as withHeld exception
-                                        throw MXCryptoError.Base(MXCryptoError.ErrorType.KEYS_WITHHELD,
-                                                withHeldInfo.code?.value ?: "",
-                                                withHeldInfo.reason)
-                                    }
+                                    // So we know that session, but it's ratcheted and we can't decrypt at that index
 
                                     if (requestKeysOnFail) {
-                                        requestKeysForEvent(event, false)
+                                        requestKeysForEvent(event)
+                                    }
+                                    // Check if partially withheld
+                                    val withHeldInfo = cryptoStore.getWithHeldMegolmSession(event.roomId, encryptedEventContent.sessionId)
+                                    if (withHeldInfo != null) {
+                                        // Encapsulate as withHeld exception
+                                        throw MXCryptoError.Base(
+                                                MXCryptoError.ErrorType.KEYS_WITHHELD,
+                                                withHeldInfo.code?.value ?: "",
+                                                withHeldInfo.reason
+                                        )
                                     }
 
                                     throw MXCryptoError.Base(
                                             MXCryptoError.ErrorType.UNKNOWN_MESSAGE_INDEX,
                                             "UNKNOWN_MESSAGE_INDEX",
-                                            null)
+                                            null
+                                    )
                                 }
 
                                 val reason = String.format(MXCryptoError.OLM_REASON, throwable.olmException.message)
@@ -153,28 +133,26 @@ internal class MXMegolmDecryption(private val userId: String,
                                 throw MXCryptoError.Base(
                                         MXCryptoError.ErrorType.OLM,
                                         reason,
-                                        detailedReason)
+                                        detailedReason
+                                )
                             }
                             if (throwable is MXCryptoError.Base) {
-                                if (
-                                /** if the session is unknown*/
-                                        throwable.errorType == MXCryptoError.ErrorType.UNKNOWN_INBOUND_SESSION_ID
-                                ) {
+                                if (throwable.errorType == MXCryptoError.ErrorType.UNKNOWN_INBOUND_SESSION_ID) {
+                                    // Check if it was withheld by sender to enrich error code
                                     val withHeldInfo = cryptoStore.getWithHeldMegolmSession(event.roomId, encryptedEventContent.sessionId)
                                     if (withHeldInfo != null) {
                                         if (requestKeysOnFail) {
-                                            requestKeysForEvent(event, true)
+                                            requestKeysForEvent(event)
                                         }
                                         // Encapsulate as withHeld exception
-                                        throw MXCryptoError.Base(MXCryptoError.ErrorType.KEYS_WITHHELD,
+                                        throw MXCryptoError.Base(
+                                                MXCryptoError.ErrorType.KEYS_WITHHELD,
                                                 withHeldInfo.code?.value ?: "",
                                                 withHeldInfo.reason)
-                                    } else {
-                                        // This is un-used in Matrix Android SDK2, not sure if needed
-                                        // addEventToPendingList(event, timeline)
-                                        if (requestKeysOnFail) {
-                                            requestKeysForEvent(event, false)
-                                        }
+                                    }
+
+                                    if (requestKeysOnFail) {
+                                        requestKeysForEvent(event)
                                     }
                                 }
                             }
@@ -190,55 +168,10 @@ internal class MXMegolmDecryption(private val userId: String,
      *
      * @param event the event
      */
-    override fun requestKeysForEvent(event: Event, withHeld: Boolean) {
-        val sender = event.senderId ?: return
-        val encryptedEventContent = event.content.toModel()
-        val senderDevice = encryptedEventContent?.deviceId ?: return
-
-        val recipients = if (event.senderId == userId || withHeld) {
-            mapOf(
-                    userId to listOf("*")
-            )
-        } else {
-            // for the case where you share the key with a device that has a broken olm session
-            // The other user might Re-shares a megolm session key with devices if the key has already been
-            // sent to them.
-            mapOf(
-                    userId to listOf("*"),
-                    sender to listOf(senderDevice)
-            )
-        }
-
-        val requestBody = RoomKeyRequestBody(
-                roomId = event.roomId,
-                algorithm = encryptedEventContent.algorithm,
-                senderKey = encryptedEventContent.senderKey,
-                sessionId = encryptedEventContent.sessionId
-        )
-
-        outgoingGossipingRequestManager.sendRoomKeyRequest(requestBody, recipients)
+    private fun requestKeysForEvent(event: Event) {
+        outgoingKeyRequestManager.requestKeyForEvent(event, false)
     }
 
-//    /**
-//     * Add an event to the list of those we couldn't decrypt the first time we
-//     * saw them.
-//     *
-//     * @param event      the event to try to decrypt later
-//     * @param timelineId the timeline identifier
-//     */
-//    private fun addEventToPendingList(event: Event, timelineId: String) {
-//        val encryptedEventContent = event.content.toModel() ?: return
-//        val pendingEventsKey = "${encryptedEventContent.senderKey}|${encryptedEventContent.sessionId}"
-//
-//        val timeline = pendingEvents.getOrPut(pendingEventsKey) { HashMap() }
-//        val events = timeline.getOrPut(timelineId) { ArrayList() }
-//
-//        if (event !in events) {
-//            Timber.v("## CRYPTO | addEventToPendingList() : add Event ${event.eventId} in room id ${event.roomId}")
-//            events.add(event)
-//        }
-//    }
-
     /**
      * Handle a key event.
      *
@@ -258,6 +191,11 @@ internal class MXMegolmDecryption(private val userId: String,
             return
         }
         if (event.getClearType() == EventType.FORWARDED_ROOM_KEY) {
+            if (!cryptoStore.isKeyGossipingEnabled()) {
+                Timber.tag(loggerTag.value)
+                        .i("onRoomKeyEvent(), ignore forward adding as per crypto config : ${roomKeyContent.roomId}|${roomKeyContent.sessionId}")
+                return
+            }
             Timber.tag(loggerTag.value).i("onRoomKeyEvent(), forward adding key : ${roomKeyContent.roomId}|${roomKeyContent.sessionId}")
             val forwardedRoomKeyContent = event.getClearContent().toModel()
                     ?: return
@@ -298,32 +236,63 @@ internal class MXMegolmDecryption(private val userId: String,
         }
 
         Timber.tag(loggerTag.value).i("onRoomKeyEvent addInboundGroupSession ${roomKeyContent.sessionId}")
-        val added = olmDevice.addInboundGroupSession(roomKeyContent.sessionId,
+        val addSessionResult = olmDevice.addInboundGroupSession(
+                roomKeyContent.sessionId,
                 roomKeyContent.sessionKey,
                 roomKeyContent.roomId,
                 senderKey,
                 forwardingCurve25519KeyChain,
                 keysClaimed,
-                exportFormat)
+                exportFormat
+        )
 
-        if (added) {
+        when (addSessionResult) {
+            is MXOlmDevice.AddSessionResult.Imported               -> addSessionResult.ratchetIndex
+            is MXOlmDevice.AddSessionResult.NotImportedHigherIndex -> addSessionResult.newIndex
+            else                                                   -> null
+        }?.let { index ->
+            if (event.getClearType() == EventType.FORWARDED_ROOM_KEY) {
+                val fromDevice = (event.content?.get("sender_key") as? String)?.let { senderDeviceIdentityKey ->
+                    cryptoStore.getUserDeviceList(event.senderId ?: "")
+                            ?.firstOrNull {
+                                it.identityKey() == senderDeviceIdentityKey
+                            }
+                }?.deviceId
+
+                outgoingKeyRequestManager.onRoomKeyForwarded(
+                        sessionId = roomKeyContent.sessionId,
+                        algorithm = roomKeyContent.algorithm ?: "",
+                        roomId = roomKeyContent.roomId,
+                        senderKey = senderKey,
+                        fromIndex = index,
+                        fromDevice = fromDevice,
+                        event = event)
+
+                cryptoStore.saveIncomingForwardKeyAuditTrail(
+                        roomId = roomKeyContent.roomId,
+                        sessionId = roomKeyContent.sessionId,
+                        senderKey = senderKey,
+                        algorithm = roomKeyContent.algorithm ?: "",
+                        userId = event.senderId ?: "",
+                        deviceId = fromDevice ?: "",
+                        chainIndex = index.toLong())
+
+                // The index is used to decide if we cancel sent request or if we wait for a better key
+                outgoingKeyRequestManager.postCancelRequestForSessionIfNeeded(roomKeyContent.sessionId, roomKeyContent.roomId, senderKey, index)
+            }
+        }
+
+        if (addSessionResult is MXOlmDevice.AddSessionResult.Imported) {
+            Timber.tag(loggerTag.value)
+                    .d("onRoomKeyEvent(${event.getClearType()}) : Added megolm session ${roomKeyContent.sessionId} in ${roomKeyContent.roomId}")
             defaultKeysBackupService.maybeBackupKeys()
 
-            val content = RoomKeyRequestBody(
-                    algorithm = roomKeyContent.algorithm,
-                    roomId = roomKeyContent.roomId,
-                    sessionId = roomKeyContent.sessionId,
-                    senderKey = senderKey
-            )
-
-            outgoingGossipingRequestManager.cancelRoomKeyRequest(content)
-
             onNewSession(roomKeyContent.roomId, senderKey, roomKeyContent.sessionId)
         }
     }
 
     /**
-     * Check if the some messages can be decrypted with a new session
+     * Check if the some messages can be decrypted with a new session.
      *
      * @param roomId the room id where the new Megolm session has been created for, may be null when importing from external sessions
      * @param senderKey the session sender key
@@ -333,77 +302,4 @@ internal class MXMegolmDecryption(private val userId: String,
         Timber.tag(loggerTag.value).v("ON NEW SESSION $sessionId - $senderKey")
         newSessionListener?.onNewSession(roomId, senderKey, sessionId)
     }
-
-    override fun hasKeysForKeyRequest(request: IncomingRoomKeyRequest): Boolean {
-        val roomId = request.requestBody?.roomId ?: return false
-        val senderKey = request.requestBody.senderKey ?: return false
-        val sessionId = request.requestBody.sessionId ?: return false
-        return olmDevice.hasInboundSessionKeys(roomId, senderKey, sessionId)
-    }
-
-    override fun shareKeysWithDevice(request: IncomingRoomKeyRequest) {
-        // sanity checks
-        if (request.requestBody == null) {
-            return
-        }
-        val userId = request.userId ?: return
-
-        cryptoCoroutineScope.launch(coroutineDispatchers.crypto) {
-            val body = request.requestBody
-            val sessionHolder = try {
-                olmDevice.getInboundGroupSession(body.sessionId, body.senderKey, body.roomId)
-            } catch (failure: Throwable) {
-                Timber.tag(loggerTag.value).e(failure, "shareKeysWithDevice: failed to get session for request $body")
-                return@launch
-            }
-
-            val export = sessionHolder.mutex.withLock {
-                sessionHolder.wrapper.exportKeys()
-            } ?: return@launch Unit.also {
-                Timber.tag(loggerTag.value).e("shareKeysWithDevice: failed to export group session ${body.sessionId}")
-            }
-
-            runCatching { deviceListManager.downloadKeys(listOf(userId), false) }
-                    .mapCatching {
-                        val deviceId = request.deviceId
-                        val deviceInfo = cryptoStore.getUserDevice(userId, deviceId ?: "")
-                        if (deviceInfo == null) {
-                            throw RuntimeException()
-                        } else {
-                            val devicesByUser = mapOf(userId to listOf(deviceInfo))
-                            val usersDeviceMap = ensureOlmSessionsForDevicesAction.handle(devicesByUser)
-                            val olmSessionResult = usersDeviceMap.getObject(userId, deviceId)
-                            if (olmSessionResult?.sessionId == null) {
-                                // no session with this device, probably because there
-                                // were no one-time keys.
-                                Timber.tag(loggerTag.value).e("no session with this device $deviceId, probably because there were no one-time keys.")
-                                return@mapCatching
-                            }
-                            Timber.tag(loggerTag.value).i("shareKeysWithDevice() : sharing session ${body.sessionId} with device $userId:$deviceId")
-
-                            val payloadJson = mapOf(
-                                    "type" to EventType.FORWARDED_ROOM_KEY,
-                                    "content" to export
-                            )
-
-                            val encodedPayload = messageEncrypter.encryptMessage(payloadJson, listOf(deviceInfo))
-                            val sendToDeviceMap = MXUsersDevicesMap()
-                            sendToDeviceMap.setObject(userId, deviceId, encodedPayload)
-                            Timber.tag(loggerTag.value).i("shareKeysWithDevice() : sending ${body.sessionId} to $userId:$deviceId")
-                            val sendToDeviceParams = SendToDeviceTask.Params(EventType.ENCRYPTED, sendToDeviceMap)
-                            try {
-                                sendToDeviceTask.execute(sendToDeviceParams)
-                            } catch (failure: Throwable) {
-                                Timber.tag(loggerTag.value).e(failure, "shareKeysWithDevice() : Failed to send ${body.sessionId} to $userId:$deviceId")
-                            }
-                        }
-                    }
-        }
-    }
-
-    override fun onRoomKeyWithHeldEvent(withHeldInfo: RoomKeyWithHeldContent) {
-        cryptoCoroutineScope.launch(coroutineDispatchers.crypto) {
-            cryptoStore.addWithHeldMegolmSession(withHeldInfo)
-        }
-    }
 }
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/algorithms/megolm/MXMegolmDecryptionFactory.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/algorithms/megolm/MXMegolmDecryptionFactory.kt
index 3eba04b9f1..096773a959 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/algorithms/megolm/MXMegolmDecryptionFactory.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/algorithms/megolm/MXMegolmDecryptionFactory.kt
@@ -17,45 +17,24 @@
 package org.matrix.android.sdk.internal.crypto.algorithms.megolm
 
 import dagger.Lazy
-import kotlinx.coroutines.CoroutineScope
-import org.matrix.android.sdk.api.MatrixCoroutineDispatchers
-import org.matrix.android.sdk.internal.crypto.DeviceListManager
 import org.matrix.android.sdk.internal.crypto.MXOlmDevice
-import org.matrix.android.sdk.internal.crypto.OutgoingGossipingRequestManager
-import org.matrix.android.sdk.internal.crypto.actions.EnsureOlmSessionsForDevicesAction
-import org.matrix.android.sdk.internal.crypto.actions.MessageEncrypter
+import org.matrix.android.sdk.internal.crypto.OutgoingKeyRequestManager
 import org.matrix.android.sdk.internal.crypto.store.IMXCryptoStore
-import org.matrix.android.sdk.internal.crypto.tasks.SendToDeviceTask
-import org.matrix.android.sdk.internal.di.UserId
 import org.matrix.android.sdk.internal.session.StreamEventsManager
 import javax.inject.Inject
 
 internal class MXMegolmDecryptionFactory @Inject constructor(
-        @UserId private val userId: String,
         private val olmDevice: MXOlmDevice,
-        private val deviceListManager: DeviceListManager,
-        private val outgoingGossipingRequestManager: OutgoingGossipingRequestManager,
-        private val messageEncrypter: MessageEncrypter,
-        private val ensureOlmSessionsForDevicesAction: EnsureOlmSessionsForDevicesAction,
+        private val outgoingKeyRequestManager: OutgoingKeyRequestManager,
         private val cryptoStore: IMXCryptoStore,
-        private val sendToDeviceTask: SendToDeviceTask,
-        private val coroutineDispatchers: MatrixCoroutineDispatchers,
-        private val cryptoCoroutineScope: CoroutineScope,
         private val eventsManager: Lazy
 ) {
 
     fun create(): MXMegolmDecryption {
         return MXMegolmDecryption(
-                userId,
                 olmDevice,
-                deviceListManager,
-                outgoingGossipingRequestManager,
-                messageEncrypter,
-                ensureOlmSessionsForDevicesAction,
+                outgoingKeyRequestManager,
                 cryptoStore,
-                sendToDeviceTask,
-                coroutineDispatchers,
-                cryptoCoroutineScope,
                 eventsManager)
     }
 }
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/algorithms/megolm/MXMegolmEncryption.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/algorithms/megolm/MXMegolmEncryption.kt
index f052194230..79e907945f 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/algorithms/megolm/MXMegolmEncryption.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/algorithms/megolm/MXMegolmEncryption.kt
@@ -28,7 +28,6 @@ import org.matrix.android.sdk.api.session.crypto.model.CryptoDeviceInfo
 import org.matrix.android.sdk.api.session.crypto.model.MXUsersDevicesMap
 import org.matrix.android.sdk.api.session.crypto.model.forEach
 import org.matrix.android.sdk.api.session.events.model.Content
-import org.matrix.android.sdk.api.session.events.model.Event
 import org.matrix.android.sdk.api.session.events.model.EventType
 import org.matrix.android.sdk.api.session.events.model.content.RoomKeyWithHeldContent
 import org.matrix.android.sdk.api.session.events.model.content.WithHeldCode
@@ -46,6 +45,7 @@ import org.matrix.android.sdk.internal.crypto.store.IMXCryptoStore
 import org.matrix.android.sdk.internal.crypto.tasks.SendToDeviceTask
 import org.matrix.android.sdk.internal.util.JsonCanonicalizer
 import org.matrix.android.sdk.internal.util.convertToUTF8
+import org.matrix.android.sdk.internal.util.time.Clock
 import timber.log.Timber
 
 private val loggerTag = LoggerTag("MXMegolmEncryption", LoggerTag.CRYPTO)
@@ -64,7 +64,8 @@ internal class MXMegolmEncryption(
         private val messageEncrypter: MessageEncrypter,
         private val warnOnUnknownDevicesRepository: WarnOnUnknownDeviceRepository,
         private val coroutineDispatchers: MatrixCoroutineDispatchers,
-        private val cryptoCoroutineScope: CoroutineScope
+        private val cryptoCoroutineScope: CoroutineScope,
+        private val clock: Clock,
 ) : IMXEncrypting, IMXGroupEncryption {
 
     // OutboundSessionInfo. Null if we haven't yet started setting one up. Note
@@ -78,7 +79,7 @@ internal class MXMegolmEncryption(
     }
 
     // Default rotation periods
-    // TODO: Make it configurable via parameters
+    // TODO Make it configurable via parameters
     // Session rotation periods
     private var sessionRotationPeriodMsgs: Int = 100
     private var sessionRotationPeriodMs: Int = 7 * 24 * 3600 * 1000
@@ -86,11 +87,11 @@ internal class MXMegolmEncryption(
     override suspend fun encryptEventContent(eventContent: Content,
                                              eventType: String,
                                              userIds: List): Content {
-        val ts = System.currentTimeMillis()
+        val ts = clock.epochMillis()
         Timber.tag(loggerTag.value).v("encryptEventContent : getDevicesInRoom")
         val devices = getDevicesInRoom(userIds)
         Timber.tag(loggerTag.value).d("encrypt event in room=$roomId - devices count in room ${devices.allowedDevices.toDebugCount()}")
-        Timber.tag(loggerTag.value).v("encryptEventContent ${System.currentTimeMillis() - ts}: getDevicesInRoom ${devices.allowedDevices.toDebugString()}")
+        Timber.tag(loggerTag.value).v("encryptEventContent ${clock.epochMillis() - ts}: getDevicesInRoom ${devices.allowedDevices.toDebugString()}")
         val outboundSession = ensureOutboundSession(devices.allowedDevices)
 
         return encryptContent(outboundSession, eventType, eventContent)
@@ -99,7 +100,7 @@ internal class MXMegolmEncryption(
                     // annoyingly we have to serialize again the saved outbound session to store message index :/
                     // if not we would see duplicate message index errors
                     olmDevice.storeOutboundGroupSessionForRoom(roomId, outboundSession.sessionId)
-                    Timber.tag(loggerTag.value).d("encrypt event in room=$roomId Finished in ${System.currentTimeMillis() - ts} millis")
+                    Timber.tag(loggerTag.value).d("encrypt event in room=$roomId Finished in ${clock.epochMillis() - ts} millis")
                 }
     }
 
@@ -125,14 +126,14 @@ internal class MXMegolmEncryption(
     }
 
     override suspend fun preshareKey(userIds: List) {
-        val ts = System.currentTimeMillis()
+        val ts = clock.epochMillis()
         Timber.tag(loggerTag.value).d("preshareKey started in $roomId ...")
         val devices = getDevicesInRoom(userIds)
         val outboundSession = ensureOutboundSession(devices.allowedDevices)
 
         notifyWithheldForSession(devices.withHeldDevices, outboundSession)
 
-        Timber.tag(loggerTag.value).d("preshareKey in $roomId done in  ${System.currentTimeMillis() - ts} millis")
+        Timber.tag(loggerTag.value).d("preshareKey in $roomId done in  ${clock.epochMillis() - ts} millis")
     }
 
     /**
@@ -148,16 +149,18 @@ internal class MXMegolmEncryption(
                 "ed25519" to olmDevice.deviceEd25519Key!!
         )
 
-        olmDevice.addInboundGroupSession(sessionId!!, olmDevice.getSessionKey(sessionId)!!, roomId, olmDevice.deviceCurve25519Key!!,
-                emptyList(), keysClaimedMap, false)
+        olmDevice.addInboundGroupSession(
+                sessionId!!, olmDevice.getSessionKey(sessionId)!!, roomId, olmDevice.deviceCurve25519Key!!,
+                emptyList(), keysClaimedMap, false
+        )
 
         defaultKeysBackupService.maybeBackupKeys()
 
-        return MXOutboundSessionInfo(sessionId, SharedWithHelper(roomId, sessionId, cryptoStore))
+        return MXOutboundSessionInfo(sessionId, SharedWithHelper(roomId, sessionId, cryptoStore), clock)
     }
 
     /**
-     * Ensure the outbound session
+     * Ensure the outbound session.
      *
      * @param devicesInRoom the devices list
      */
@@ -193,7 +196,7 @@ internal class MXMegolmEncryption(
     }
 
     /**
-     * Share the device key to a list of users
+     * Share the device key to a list of users.
      *
      * @param session        the session info
      * @param devicesByUsers the devices map
@@ -222,7 +225,7 @@ internal class MXMegolmEncryption(
     }
 
     /**
-     * Share the device keys of a an user
+     * Share the device keys of a an user.
      *
      * @param session       the session info
      * @param devicesByUser the devices map
@@ -243,12 +246,12 @@ internal class MXMegolmEncryption(
         payload["type"] = EventType.ROOM_KEY
         payload["content"] = submap
 
-        var t0 = System.currentTimeMillis()
+        var t0 = clock.epochMillis()
         Timber.tag(loggerTag.value).v("shareUserDevicesKey() : starts")
 
         val results = ensureOlmSessionsForDevicesAction.handle(devicesByUser)
         Timber.tag(loggerTag.value).v(
-                """shareUserDevicesKey(): ensureOlmSessionsForDevices succeeds after ${System.currentTimeMillis() - t0} ms"""
+                """shareUserDevicesKey(): ensureOlmSessionsForDevices succeeds after ${clock.epochMillis() - t0} ms"""
                         .trimMargin()
         )
         val contentMap = MXUsersDevicesMap()
@@ -281,27 +284,16 @@ internal class MXMegolmEncryption(
         // attempted to share with) rather than the contentMap (those we did
         // share with), because we don't want to try to claim a one-time-key
         // for dead devices on every message.
-        val gossipingEventBuffer = arrayListOf()
-        for ((userId, devicesToShareWith) in devicesByUser) {
+        for ((_, devicesToShareWith) in devicesByUser) {
             for (deviceInfo in devicesToShareWith) {
                 session.sharedWithHelper.markedSessionAsShared(deviceInfo, chainIndex)
-                gossipingEventBuffer.add(
-                        Event(
-                                type = EventType.ROOM_KEY,
-                                senderId = myUserId,
-                                content = submap.apply {
-                                    this["session_key"] = ""
-                                    // we add a fake key for trail
-                                    this["_dest"] = "$userId|${deviceInfo.deviceId}"
-                                }
-                        ))
+                // XXX is it needed to add it to the audit trail?
+                // For now decided that no, we are more interested by forward trail
             }
         }
 
-        cryptoStore.saveGossipingEvents(gossipingEventBuffer)
-
         if (haveTargets) {
-            t0 = System.currentTimeMillis()
+            t0 = clock.epochMillis()
             Timber.tag(loggerTag.value).i("shareUserDevicesKey() ${session.sessionId} : has target")
             Timber.tag(loggerTag.value).d("sending to device room key for ${session.sessionId} to ${contentMap.toDebugString()}")
             val sendToDeviceParams = SendToDeviceTask.Params(EventType.ENCRYPTED, contentMap)
@@ -309,7 +301,7 @@ internal class MXMegolmEncryption(
                 withContext(coroutineDispatchers.io) {
                     sendToDeviceTask.execute(sendToDeviceParams)
                 }
-                Timber.tag(loggerTag.value).i("shareUserDevicesKey() : sendToDevice succeeds after ${System.currentTimeMillis() - t0} ms")
+                Timber.tag(loggerTag.value).i("shareUserDevicesKey() : sendToDevice succeeds after ${clock.epochMillis() - t0} ms")
             } catch (failure: Throwable) {
                 // What to do here...
                 Timber.tag(loggerTag.value).e("shareUserDevicesKey() : Failed to share <${session.sessionId}>")
@@ -333,14 +325,17 @@ internal class MXMegolmEncryption(
                                           sessionId: String,
                                           senderKey: String?,
                                           code: WithHeldCode) {
-        Timber.tag(loggerTag.value).d("notifyKeyWithHeld() :sending withheld for session:$sessionId and code $code to" +
-                " ${targets.joinToString { "${it.userId}|${it.deviceId}" }}")
+        Timber.tag(loggerTag.value).d(
+                "notifyKeyWithHeld() :sending withheld for session:$sessionId and code $code to" +
+                        " ${targets.joinToString { "${it.userId}|${it.deviceId}" }}"
+        )
         val withHeldContent = RoomKeyWithHeldContent(
                 roomId = roomId,
                 senderKey = senderKey,
                 algorithm = MXCRYPTO_ALGORITHM_MEGOLM,
                 sessionId = sessionId,
-                codeString = code.value
+                codeString = code.value,
+                fromDevice = myDeviceId
         )
         val params = SendToDeviceTask.Params(
                 EventType.ROOM_KEY_WITHHELD,
@@ -361,7 +356,7 @@ internal class MXMegolmEncryption(
     }
 
     /**
-     * process the pending encryptions
+     * process the pending encryptions.
      */
     private fun encryptContent(session: MXOutboundSessionInfo, eventType: String, eventContent: Content): Content {
         // Everything is in place, encrypt all pending events
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/algorithms/megolm/MXMegolmEncryptionFactory.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/algorithms/megolm/MXMegolmEncryptionFactory.kt
index 136fdc05f5..4225d604aa 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/algorithms/megolm/MXMegolmEncryptionFactory.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/algorithms/megolm/MXMegolmEncryptionFactory.kt
@@ -28,6 +28,7 @@ import org.matrix.android.sdk.internal.crypto.store.IMXCryptoStore
 import org.matrix.android.sdk.internal.crypto.tasks.SendToDeviceTask
 import org.matrix.android.sdk.internal.di.DeviceId
 import org.matrix.android.sdk.internal.di.UserId
+import org.matrix.android.sdk.internal.util.time.Clock
 import javax.inject.Inject
 
 internal class MXMegolmEncryptionFactory @Inject constructor(
@@ -42,7 +43,9 @@ internal class MXMegolmEncryptionFactory @Inject constructor(
         private val messageEncrypter: MessageEncrypter,
         private val warnOnUnknownDevicesRepository: WarnOnUnknownDeviceRepository,
         private val coroutineDispatchers: MatrixCoroutineDispatchers,
-        private val cryptoCoroutineScope: CoroutineScope) {
+        private val cryptoCoroutineScope: CoroutineScope,
+        private val clock: Clock,
+) {
 
     fun create(roomId: String): MXMegolmEncryption {
         return MXMegolmEncryption(
@@ -58,7 +61,8 @@ internal class MXMegolmEncryptionFactory @Inject constructor(
                 messageEncrypter = messageEncrypter,
                 warnOnUnknownDevicesRepository = warnOnUnknownDevicesRepository,
                 coroutineDispatchers = coroutineDispatchers,
-                cryptoCoroutineScope = cryptoCoroutineScope
+                cryptoCoroutineScope = cryptoCoroutineScope,
+                clock = clock,
         )
     }
 }
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/algorithms/megolm/MXOutboundSessionInfo.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/algorithms/megolm/MXOutboundSessionInfo.kt
index 091abd4974..28d925d8fd 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/algorithms/megolm/MXOutboundSessionInfo.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/algorithms/megolm/MXOutboundSessionInfo.kt
@@ -18,21 +18,24 @@ package org.matrix.android.sdk.internal.crypto.algorithms.megolm
 
 import org.matrix.android.sdk.api.session.crypto.model.CryptoDeviceInfo
 import org.matrix.android.sdk.api.session.crypto.model.MXUsersDevicesMap
+import org.matrix.android.sdk.internal.util.time.Clock
 import timber.log.Timber
 
 internal class MXOutboundSessionInfo(
         // The id of the session
         val sessionId: String,
         val sharedWithHelper: SharedWithHelper,
+        private val clock: Clock,
         // When the session was created
-        private val creationTime: Long = System.currentTimeMillis()) {
+        private val creationTime: Long = clock.epochMillis(),
+) {
 
     // Number of times this session has been used
     var useCount: Int = 0
 
     fun needsRotation(rotationPeriodMsgs: Int, rotationPeriodMs: Int): Boolean {
         var needsRotation = false
-        val sessionLifetime = System.currentTimeMillis() - creationTime
+        val sessionLifetime = clock.epochMillis() - creationTime
 
         if (useCount >= rotationPeriodMsgs || sessionLifetime >= rotationPeriodMs) {
             Timber.v("## needsRotation() : Rotating megolm session after $useCount, ${sessionLifetime}ms")
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/algorithms/megolm/SharedWithHelper.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/algorithms/megolm/SharedWithHelper.kt
index 59d78c3e05..61ad345c62 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/algorithms/megolm/SharedWithHelper.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/algorithms/megolm/SharedWithHelper.kt
@@ -36,6 +36,7 @@ internal class SharedWithHelper(
                 userId = deviceInfo.userId,
                 deviceId = deviceInfo.deviceId,
                 deviceIdentityKey = deviceInfo.identityKey() ?: "",
-                chainIndex = chainIndex)
+                chainIndex = chainIndex
+        )
     }
 }
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/algorithms/olm/MXOlmDecryption.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/algorithms/olm/MXOlmDecryption.kt
index 0db8700852..1e66fe84c9 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/algorithms/olm/MXOlmDecryption.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/algorithms/olm/MXOlmDecryption.kt
@@ -45,20 +45,26 @@ internal class MXOlmDecryption(
     override suspend fun decryptEvent(event: Event, timeline: String): MXEventDecryptionResult {
         val olmEventContent = event.content.toModel() ?: run {
             Timber.tag(loggerTag.value).e("## decryptEvent() : bad event format")
-            throw MXCryptoError.Base(MXCryptoError.ErrorType.BAD_EVENT_FORMAT,
-                    MXCryptoError.BAD_EVENT_FORMAT_TEXT_REASON)
+            throw MXCryptoError.Base(
+                    MXCryptoError.ErrorType.BAD_EVENT_FORMAT,
+                    MXCryptoError.BAD_EVENT_FORMAT_TEXT_REASON
+            )
         }
 
         val cipherText = olmEventContent.ciphertext ?: run {
             Timber.tag(loggerTag.value).e("## decryptEvent() : missing cipher text")
-            throw MXCryptoError.Base(MXCryptoError.ErrorType.MISSING_CIPHER_TEXT,
-                    MXCryptoError.MISSING_CIPHER_TEXT_REASON)
+            throw MXCryptoError.Base(
+                    MXCryptoError.ErrorType.MISSING_CIPHER_TEXT,
+                    MXCryptoError.MISSING_CIPHER_TEXT_REASON
+            )
         }
 
         val senderKey = olmEventContent.senderKey ?: run {
             Timber.tag(loggerTag.value).e("## decryptEvent() : missing sender key")
-            throw MXCryptoError.Base(MXCryptoError.ErrorType.MISSING_SENDER_KEY,
-                    MXCryptoError.MISSING_SENDER_KEY_TEXT_REASON)
+            throw MXCryptoError.Base(
+                    MXCryptoError.ErrorType.MISSING_SENDER_KEY,
+                    MXCryptoError.MISSING_SENDER_KEY_TEXT_REASON
+            )
         }
 
         val messageAny = cipherText[olmDevice.deviceCurve25519Key] ?: run {
@@ -98,52 +104,70 @@ internal class MXOlmDecryption(
         }
 
         if (olmPayloadContent.recipient != userId) {
-            Timber.tag(loggerTag.value).e("## decryptEvent() : Event ${event.eventId}:" +
-                    " Intended recipient ${olmPayloadContent.recipient} does not match our id $userId")
-            throw MXCryptoError.Base(MXCryptoError.ErrorType.BAD_RECIPIENT,
-                    String.format(MXCryptoError.BAD_RECIPIENT_REASON, olmPayloadContent.recipient))
+            Timber.tag(loggerTag.value).e(
+                    "## decryptEvent() : Event ${event.eventId}:" +
+                            " Intended recipient ${olmPayloadContent.recipient} does not match our id $userId"
+            )
+            throw MXCryptoError.Base(
+                    MXCryptoError.ErrorType.BAD_RECIPIENT,
+                    String.format(MXCryptoError.BAD_RECIPIENT_REASON, olmPayloadContent.recipient)
+            )
         }
 
         val recipientKeys = olmPayloadContent.recipientKeys ?: run {
-            Timber.tag(loggerTag.value).e("## decryptEvent() : Olm event (id=${event.eventId}) contains no 'recipient_keys'" +
-                    " property; cannot prevent unknown-key attack")
-            throw MXCryptoError.Base(MXCryptoError.ErrorType.MISSING_PROPERTY,
-                    String.format(MXCryptoError.ERROR_MISSING_PROPERTY_REASON, "recipient_keys"))
+            Timber.tag(loggerTag.value).e(
+                    "## decryptEvent() : Olm event (id=${event.eventId}) contains no 'recipient_keys'" +
+                            " property; cannot prevent unknown-key attack"
+            )
+            throw MXCryptoError.Base(
+                    MXCryptoError.ErrorType.MISSING_PROPERTY,
+                    String.format(MXCryptoError.ERROR_MISSING_PROPERTY_REASON, "recipient_keys")
+            )
         }
 
         val ed25519 = recipientKeys["ed25519"]
 
         if (ed25519 != olmDevice.deviceEd25519Key) {
             Timber.tag(loggerTag.value).e("## decryptEvent() : Event ${event.eventId}: Intended recipient ed25519 key $ed25519 did not match ours")
-            throw MXCryptoError.Base(MXCryptoError.ErrorType.BAD_RECIPIENT_KEY,
-                    MXCryptoError.BAD_RECIPIENT_KEY_REASON)
+            throw MXCryptoError.Base(
+                    MXCryptoError.ErrorType.BAD_RECIPIENT_KEY,
+                    MXCryptoError.BAD_RECIPIENT_KEY_REASON
+            )
         }
 
         if (olmPayloadContent.sender.isNullOrBlank()) {
             Timber.tag(loggerTag.value)
                     .e("## decryptEvent() : Olm event (id=${event.eventId}) contains no 'sender' property; cannot prevent unknown-key attack")
-            throw MXCryptoError.Base(MXCryptoError.ErrorType.MISSING_PROPERTY,
-                    String.format(MXCryptoError.ERROR_MISSING_PROPERTY_REASON, "sender"))
+            throw MXCryptoError.Base(
+                    MXCryptoError.ErrorType.MISSING_PROPERTY,
+                    String.format(MXCryptoError.ERROR_MISSING_PROPERTY_REASON, "sender")
+            )
         }
 
         if (olmPayloadContent.sender != event.senderId) {
             Timber.tag(loggerTag.value)
                     .e("Event ${event.eventId}:  sender ${olmPayloadContent.sender} does not match reported sender ${event.senderId}")
-            throw MXCryptoError.Base(MXCryptoError.ErrorType.FORWARDED_MESSAGE,
-                    String.format(MXCryptoError.FORWARDED_MESSAGE_REASON, olmPayloadContent.sender))
+            throw MXCryptoError.Base(
+                    MXCryptoError.ErrorType.FORWARDED_MESSAGE,
+                    String.format(MXCryptoError.FORWARDED_MESSAGE_REASON, olmPayloadContent.sender)
+            )
         }
 
         if (olmPayloadContent.roomId != event.roomId) {
             Timber.tag(loggerTag.value)
                     .e("## decryptEvent() : Event ${event.eventId}:  room ${olmPayloadContent.roomId} does not match reported room ${event.roomId}")
-            throw MXCryptoError.Base(MXCryptoError.ErrorType.BAD_ROOM,
-                    String.format(MXCryptoError.BAD_ROOM_REASON, olmPayloadContent.roomId))
+            throw MXCryptoError.Base(
+                    MXCryptoError.ErrorType.BAD_ROOM,
+                    String.format(MXCryptoError.BAD_ROOM_REASON, olmPayloadContent.roomId)
+            )
         }
 
         val keys = olmPayloadContent.keys ?: run {
             Timber.tag(loggerTag.value).e("## decryptEvent failed : null keys")
-            throw MXCryptoError.Base(MXCryptoError.ErrorType.UNABLE_TO_DECRYPT,
-                    MXCryptoError.MISSING_CIPHER_TEXT_REASON)
+            throw MXCryptoError.Base(
+                    MXCryptoError.ErrorType.UNABLE_TO_DECRYPT,
+                    MXCryptoError.MISSING_CIPHER_TEXT_REASON
+            )
         }
 
         return MXEventDecryptionResult(
@@ -241,8 +265,4 @@ internal class MXOlmDecryption(
 
         return res["payload"]
     }
-
-    override fun requestKeysForEvent(event: Event, withHeld: Boolean) {
-        // nop
-    }
 }
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/algorithms/olm/MXOlmDecryptionFactory.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/algorithms/olm/MXOlmDecryptionFactory.kt
index 972176e25b..d5c5e85e41 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/algorithms/olm/MXOlmDecryptionFactory.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/algorithms/olm/MXOlmDecryptionFactory.kt
@@ -26,6 +26,7 @@ internal class MXOlmDecryptionFactory @Inject constructor(private val olmDevice:
     fun create(): MXOlmDecryption {
         return MXOlmDecryption(
                 olmDevice,
-                userId)
+                userId
+        )
     }
 }
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/algorithms/olm/MXOlmEncryption.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/algorithms/olm/MXOlmEncryption.kt
index 7fdfd5a287..3c9706abe1 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/algorithms/olm/MXOlmEncryption.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/algorithms/olm/MXOlmEncryption.kt
@@ -33,12 +33,12 @@ internal class MXOlmEncryption(
         private val messageEncrypter: MessageEncrypter,
         private val deviceListManager: DeviceListManager,
         private val ensureOlmSessionsForUsersAction: EnsureOlmSessionsForUsersAction) :
-    IMXEncrypting {
+        IMXEncrypting {
 
     override suspend fun encryptEventContent(eventContent: Content, eventType: String, userIds: List): Content {
         // pick the list of recipients based on the membership list.
         //
-        // TODO: there is a race condition here! What if a new user turns up
+        // TODO there is a race condition here! What if a new user turns up
         ensureSession(userIds)
         val deviceInfos = ArrayList()
         for (userId in userIds) {
@@ -68,7 +68,7 @@ internal class MXOlmEncryption(
     }
 
     /**
-     * Ensure that the session
+     * Ensure that the session.
      *
      * @param users    the user ids list
      */
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/algorithms/olm/MXOlmEncryptionFactory.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/algorithms/olm/MXOlmEncryptionFactory.kt
index 50ce2d2bf3..44e55900e4 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/algorithms/olm/MXOlmEncryptionFactory.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/algorithms/olm/MXOlmEncryptionFactory.kt
@@ -38,6 +38,7 @@ internal class MXOlmEncryptionFactory @Inject constructor(private val olmDevice:
                 cryptoStore,
                 messageEncrypter,
                 deviceListManager,
-                ensureOlmSessionsForUsersAction)
+                ensureOlmSessionsForUsersAction
+        )
     }
 }
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/attachments/EncryptionResult.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/attachments/EncryptionResult.kt
index 80090cf4a8..95254bd679 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/attachments/EncryptionResult.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/attachments/EncryptionResult.kt
@@ -19,7 +19,7 @@ package org.matrix.android.sdk.internal.crypto.attachments
 import org.matrix.android.sdk.api.session.crypto.model.EncryptedFileInfo
 
 /**
- * Define the result of an encryption file
+ * Define the result of an encryption file.
  */
 internal data class EncryptionResult(
         val encryptedFileInfo: EncryptedFileInfo,
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/attachments/MXEncryptedAttachments.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/attachments/MXEncryptedAttachments.kt
index 91b6af6fc3..b4cbd15109 100755
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/attachments/MXEncryptedAttachments.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/attachments/MXEncryptedAttachments.kt
@@ -23,6 +23,7 @@ import org.matrix.android.sdk.api.session.crypto.model.EncryptedFileKey
 import org.matrix.android.sdk.internal.util.base64ToBase64Url
 import org.matrix.android.sdk.internal.util.base64ToUnpaddedBase64
 import org.matrix.android.sdk.internal.util.base64UrlToBase64
+import org.matrix.android.sdk.internal.util.time.Clock
 import timber.log.Timber
 import java.io.ByteArrayOutputStream
 import java.io.File
@@ -42,8 +43,9 @@ internal object MXEncryptedAttachments {
 
     fun encrypt(clearStream: InputStream,
                 outputFile: File,
+                clock: Clock,
                 progress: ((current: Int, total: Int) -> Unit)): EncryptedFileInfo {
-        val t0 = System.currentTimeMillis()
+        val t0 = clock.epochMillis()
         val secureRandom = SecureRandom()
         val initVectorBytes = ByteArray(16) { 0.toByte() }
 
@@ -100,7 +102,7 @@ internal object MXEncryptedAttachments {
                 hashes = mapOf("sha256" to base64ToUnpaddedBase64(Base64.encodeToString(messageDigest.digest(), Base64.DEFAULT))),
                 v = "v2"
         )
-                .also { Timber.v("Encrypt in ${System.currentTimeMillis() - t0}ms") }
+                .also { Timber.v("Encrypt in ${clock.epochMillis() - t0}ms") }
     }
 
 //    fun cipherInputStream(attachmentStream: InputStream, mimetype: String?): Pair {
@@ -159,8 +161,8 @@ internal object MXEncryptedAttachments {
      * @param attachmentStream the attachment stream. Will be closed after this method call.
      * @return the encryption file info
      */
-    fun encryptAttachment(attachmentStream: InputStream): EncryptionResult {
-        val t0 = System.currentTimeMillis()
+    fun encryptAttachment(attachmentStream: InputStream, clock: Clock): EncryptionResult {
+        val t0 = clock.epochMillis()
         val secureRandom = SecureRandom()
 
         // generate a random iv key
@@ -221,11 +223,11 @@ internal object MXEncryptedAttachments {
                 ),
                 encryptedByteArray = byteArrayOutputStream.toByteArray()
         )
-                .also { Timber.v("Encrypt in ${System.currentTimeMillis() - t0}ms") }
+                .also { Timber.v("Encrypt in ${clock.epochMillis() - t0}ms") }
     }
 
     /**
-     * Decrypt an attachment
+     * Decrypt an attachment.
      *
      * @param attachmentStream the attachment stream. Will be closed after this method call.
      * @param elementToDecrypt the elementToDecrypt info
@@ -234,14 +236,16 @@ internal object MXEncryptedAttachments {
      */
     fun decryptAttachment(attachmentStream: InputStream?,
                           elementToDecrypt: ElementToDecrypt?,
-                          outputStream: OutputStream): Boolean {
+                          outputStream: OutputStream,
+                          clock: Clock
+    ): Boolean {
         // sanity checks
         if (null == attachmentStream || elementToDecrypt == null) {
             Timber.e("## decryptAttachment() : null stream")
             return false
         }
 
-        val t0 = System.currentTimeMillis()
+        val t0 = clock.epochMillis()
 
         try {
             val key = Base64.decode(base64UrlToBase64(elementToDecrypt.k), Base64.DEFAULT)
@@ -279,7 +283,8 @@ internal object MXEncryptedAttachments {
                 return false
             }
 
-            return true.also { Timber.v("Decrypt in ${System.currentTimeMillis() - t0}ms") }
+            Timber.v("Decrypt in ${clock.epochMillis() - t0} ms")
+            return true
         } catch (oom: OutOfMemoryError) {
             Timber.e(oom, "## decryptAttachment() failed: OOM")
         } catch (e: Exception) {
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/crosssigning/CrossSigningOlm.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/crosssigning/CrossSigningOlm.kt
new file mode 100644
index 0000000000..4fa355cd2a
--- /dev/null
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/crosssigning/CrossSigningOlm.kt
@@ -0,0 +1,93 @@
+/*
+ * Copyright 2022 The Matrix.org Foundation C.I.C.
+ *
+ * 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 org.matrix.android.sdk.internal.crypto.crosssigning
+
+import org.matrix.android.sdk.api.util.JsonDict
+import org.matrix.android.sdk.internal.crypto.store.IMXCryptoStore
+import org.matrix.android.sdk.internal.session.SessionScope
+import org.matrix.android.sdk.internal.util.JsonCanonicalizer
+import org.matrix.olm.OlmPkSigning
+import org.matrix.olm.OlmUtility
+import javax.inject.Inject
+
+/**
+ * Holds the OlmPkSigning for cross signing.
+ * Can be injected without having to get the full cross signing service
+ */
+@SessionScope
+internal class CrossSigningOlm @Inject constructor(
+        private val cryptoStore: IMXCryptoStore,
+) {
+
+    enum class KeyType {
+        SELF,
+        USER,
+        MASTER
+    }
+
+    var olmUtility: OlmUtility = OlmUtility()
+
+    var masterPkSigning: OlmPkSigning? = null
+    var userPkSigning: OlmPkSigning? = null
+    var selfSigningPkSigning: OlmPkSigning? = null
+
+    fun release() {
+        olmUtility.releaseUtility()
+        listOf(masterPkSigning, userPkSigning, selfSigningPkSigning).forEach { it?.releaseSigning() }
+    }
+
+    fun signObject(type: KeyType, strToSign: String): Map {
+        val myKeys = cryptoStore.getMyCrossSigningInfo()
+        val pubKey = when (type) {
+            KeyType.SELF   -> myKeys?.selfSigningKey()
+            KeyType.USER   -> myKeys?.userKey()
+            KeyType.MASTER -> myKeys?.masterKey()
+        }?.unpaddedBase64PublicKey
+        val pkSigning = when (type) {
+            KeyType.SELF   -> selfSigningPkSigning
+            KeyType.USER   -> userPkSigning
+            KeyType.MASTER -> masterPkSigning
+        }
+        if (pubKey == null || pkSigning == null) {
+            throw Throwable("Cannot sign from this account, public and/or privateKey Unknown $type|$pkSigning")
+        }
+        val signature = pkSigning.sign(strToSign)
+        return mapOf(
+                "ed25519:$pubKey" to signature
+        )
+    }
+
+    fun verifySignature(type: KeyType, signable: JsonDict, signatures: Map>) {
+        val myKeys = cryptoStore.getMyCrossSigningInfo()
+                ?: throw NoSuchElementException("Cross Signing not configured")
+        val myUserID = myKeys.userId
+        val pubKey = when (type) {
+            KeyType.SELF   -> myKeys.selfSigningKey()
+            KeyType.USER   -> myKeys.userKey()
+            KeyType.MASTER -> myKeys.masterKey()
+        }?.unpaddedBase64PublicKey ?: throw NoSuchElementException("Cross Signing not configured")
+        val signaturesMadeByMyKey = signatures[myUserID] // Signatures made by me
+                ?.get("ed25519:$pubKey")
+
+        if (signaturesMadeByMyKey.isNullOrBlank()) {
+            throw IllegalArgumentException("Not signed with my key $type")
+        }
+
+        // Check that Alice USK signature of Bob MSK is valid
+        olmUtility.verifyEd25519Signature(signaturesMadeByMyKey, pubKey, JsonCanonicalizer.getCanonicalJson(Map::class.java, signable))
+    }
+}
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/crosssigning/DefaultCrossSigningService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/crosssigning/DefaultCrossSigningService.kt
index ba1718688f..6c198abc2e 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/crosssigning/DefaultCrossSigningService.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/crosssigning/DefaultCrossSigningService.kt
@@ -38,6 +38,7 @@ import org.matrix.android.sdk.api.session.crypto.model.CryptoDeviceInfo
 import org.matrix.android.sdk.api.util.Optional
 import org.matrix.android.sdk.api.util.fromBase64
 import org.matrix.android.sdk.internal.crypto.DeviceListManager
+import org.matrix.android.sdk.internal.crypto.OutgoingKeyRequestManager
 import org.matrix.android.sdk.internal.crypto.model.rest.UploadSignatureQueryBuilder
 import org.matrix.android.sdk.internal.crypto.store.IMXCryptoStore
 import org.matrix.android.sdk.internal.crypto.tasks.InitializeCrossSigningTask
@@ -53,7 +54,6 @@ import org.matrix.android.sdk.internal.util.JsonCanonicalizer
 import org.matrix.android.sdk.internal.util.logLimit
 import org.matrix.android.sdk.internal.worker.WorkerParamsFactory
 import org.matrix.olm.OlmPkSigning
-import org.matrix.olm.OlmUtility
 import timber.log.Timber
 import java.util.concurrent.TimeUnit
 import javax.inject.Inject
@@ -70,19 +70,14 @@ internal class DefaultCrossSigningService @Inject constructor(
         private val coroutineDispatchers: MatrixCoroutineDispatchers,
         private val cryptoCoroutineScope: CoroutineScope,
         private val workManagerProvider: WorkManagerProvider,
+        private val outgoingKeyRequestManager: OutgoingKeyRequestManager,
+        private val crossSigningOlm: CrossSigningOlm,
         private val updateTrustWorkerDataRepository: UpdateTrustWorkerDataRepository
 ) : CrossSigningService,
         DeviceListManager.UserDevicesUpdateListener {
 
-    private var olmUtility: OlmUtility? = null
-
-    private var masterPkSigning: OlmPkSigning? = null
-    private var userPkSigning: OlmPkSigning? = null
-    private var selfSigningPkSigning: OlmPkSigning? = null
-
     init {
         try {
-            olmUtility = OlmUtility()
 
             // Try to get stored keys if they exist
             cryptoStore.getMyCrossSigningInfo()?.let { mxCrossSigningInfo ->
@@ -95,7 +90,7 @@ internal class DefaultCrossSigningService @Inject constructor(
                             ?.let { privateKeySeed ->
                                 val pkSigning = OlmPkSigning()
                                 if (pkSigning.initWithSeed(privateKeySeed) == mxCrossSigningInfo.masterKey()?.unpaddedBase64PublicKey) {
-                                    masterPkSigning = pkSigning
+                                    crossSigningOlm.masterPkSigning = pkSigning
                                     Timber.i("## CrossSigning - Loading master key success")
                                 } else {
                                     Timber.w("## CrossSigning - Public master key does not match the private key")
@@ -108,7 +103,7 @@ internal class DefaultCrossSigningService @Inject constructor(
                             ?.let { privateKeySeed ->
                                 val pkSigning = OlmPkSigning()
                                 if (pkSigning.initWithSeed(privateKeySeed) == mxCrossSigningInfo.userKey()?.unpaddedBase64PublicKey) {
-                                    userPkSigning = pkSigning
+                                    crossSigningOlm.userPkSigning = pkSigning
                                     Timber.i("## CrossSigning - Loading User Signing key success")
                                 } else {
                                     Timber.w("## CrossSigning - Public User key does not match the private key")
@@ -121,7 +116,7 @@ internal class DefaultCrossSigningService @Inject constructor(
                             ?.let { privateKeySeed ->
                                 val pkSigning = OlmPkSigning()
                                 if (pkSigning.initWithSeed(privateKeySeed) == mxCrossSigningInfo.selfSigningKey()?.unpaddedBase64PublicKey) {
-                                    selfSigningPkSigning = pkSigning
+                                    crossSigningOlm.selfSigningPkSigning = pkSigning
                                     Timber.i("## CrossSigning - Loading Self Signing key success")
                                 } else {
                                     Timber.w("## CrossSigning - Public Self Signing key does not match the private key")
@@ -143,8 +138,7 @@ internal class DefaultCrossSigningService @Inject constructor(
     }
 
     fun release() {
-        olmUtility?.releaseUtility()
-        listOf(masterPkSigning, userPkSigning, selfSigningPkSigning).forEach { it?.releaseSigning() }
+        crossSigningOlm.release()
         deviceListManager.removeListener(this)
     }
 
@@ -153,10 +147,10 @@ internal class DefaultCrossSigningService @Inject constructor(
     }
 
     /**
-     *   - Make 3 key pairs (MSK, USK, SSK)
-     *   - Save the private keys with proper security
-     *   - Sign the keys and upload them
-     *   - Sign the current device with SSK and sign MSK with device key (migration) and upload signatures
+     * - Make 3 key pairs (MSK, USK, SSK)
+     * - Save the private keys with proper security
+     * - Sign the keys and upload them
+     * - Sign the current device with SSK and sign MSK with device key (migration) and upload signatures.
      */
     override fun initializeCrossSigning(uiaInterceptor: UserInteractiveAuthInterceptor?, callback: MatrixCallback) {
         Timber.d("## CrossSigning  initializeCrossSigning")
@@ -177,9 +171,9 @@ internal class DefaultCrossSigningService @Inject constructor(
                     cryptoStore.setMyCrossSigningInfo(crossSigningInfo)
                     setUserKeysAsTrusted(userId, true)
                     cryptoStore.storePrivateKeysInfo(data.masterKeyPK, data.userKeyPK, data.selfSigningKeyPK)
-                    masterPkSigning = OlmPkSigning().apply { initWithSeed(data.masterKeyPK.fromBase64()) }
-                    userPkSigning = OlmPkSigning().apply { initWithSeed(data.userKeyPK.fromBase64()) }
-                    selfSigningPkSigning = OlmPkSigning().apply { initWithSeed(data.selfSigningKeyPK.fromBase64()) }
+                    crossSigningOlm.masterPkSigning = OlmPkSigning().apply { initWithSeed(data.masterKeyPK.fromBase64()) }
+                    crossSigningOlm.userPkSigning = OlmPkSigning().apply { initWithSeed(data.userKeyPK.fromBase64()) }
+                    crossSigningOlm.selfSigningPkSigning = OlmPkSigning().apply { initWithSeed(data.selfSigningKeyPK.fromBase64()) }
 
                     callback.onSuccess(Unit)
                 }
@@ -198,8 +192,8 @@ internal class DefaultCrossSigningService @Inject constructor(
                     val pkSigning = OlmPkSigning()
                     try {
                         if (pkSigning.initWithSeed(privateKeySeed) == mxCrossSigningInfo.masterKey()?.unpaddedBase64PublicKey) {
-                            masterPkSigning?.releaseSigning()
-                            masterPkSigning = pkSigning
+                            crossSigningOlm.masterPkSigning?.releaseSigning()
+                            crossSigningOlm.masterPkSigning = pkSigning
                             Timber.i("## CrossSigning - Loading MSK success")
                             cryptoStore.storeMSKPrivateKey(mskPrivateKey)
                             return
@@ -225,8 +219,8 @@ internal class DefaultCrossSigningService @Inject constructor(
                     val pkSigning = OlmPkSigning()
                     try {
                         if (pkSigning.initWithSeed(privateKeySeed) == mxCrossSigningInfo.selfSigningKey()?.unpaddedBase64PublicKey) {
-                            selfSigningPkSigning?.releaseSigning()
-                            selfSigningPkSigning = pkSigning
+                            crossSigningOlm.selfSigningPkSigning?.releaseSigning()
+                            crossSigningOlm.selfSigningPkSigning = pkSigning
                             Timber.i("## CrossSigning - Loading SSK success")
                             cryptoStore.storeSSKPrivateKey(sskPrivateKey)
                             return
@@ -252,8 +246,8 @@ internal class DefaultCrossSigningService @Inject constructor(
                     val pkSigning = OlmPkSigning()
                     try {
                         if (pkSigning.initWithSeed(privateKeySeed) == mxCrossSigningInfo.userKey()?.unpaddedBase64PublicKey) {
-                            userPkSigning?.releaseSigning()
-                            userPkSigning = pkSigning
+                            crossSigningOlm.userPkSigning?.releaseSigning()
+                            crossSigningOlm.userPkSigning = pkSigning
                             Timber.i("## CrossSigning - Loading USK success")
                             cryptoStore.storeUSKPrivateKey(uskPrivateKey)
                             return
@@ -282,8 +276,8 @@ internal class DefaultCrossSigningService @Inject constructor(
                     val pkSigning = OlmPkSigning()
                     try {
                         if (pkSigning.initWithSeed(privateKeySeed) == mxCrossSigningInfo.masterKey()?.unpaddedBase64PublicKey) {
-                            masterPkSigning?.releaseSigning()
-                            masterPkSigning = pkSigning
+                            crossSigningOlm.masterPkSigning?.releaseSigning()
+                            crossSigningOlm.masterPkSigning = pkSigning
                             masterKeyIsTrusted = true
                             Timber.i("## CrossSigning - Loading master key success")
                         } else {
@@ -299,8 +293,8 @@ internal class DefaultCrossSigningService @Inject constructor(
                     val pkSigning = OlmPkSigning()
                     try {
                         if (pkSigning.initWithSeed(privateKeySeed) == mxCrossSigningInfo.userKey()?.unpaddedBase64PublicKey) {
-                            userPkSigning?.releaseSigning()
-                            userPkSigning = pkSigning
+                            crossSigningOlm.userPkSigning?.releaseSigning()
+                            crossSigningOlm.userPkSigning = pkSigning
                             userKeyIsTrusted = true
                             Timber.i("## CrossSigning - Loading master key success")
                         } else {
@@ -316,8 +310,8 @@ internal class DefaultCrossSigningService @Inject constructor(
                     val pkSigning = OlmPkSigning()
                     try {
                         if (pkSigning.initWithSeed(privateKeySeed) == mxCrossSigningInfo.selfSigningKey()?.unpaddedBase64PublicKey) {
-                            selfSigningPkSigning?.releaseSigning()
-                            selfSigningPkSigning = pkSigning
+                            crossSigningOlm.selfSigningPkSigning?.releaseSigning()
+                            crossSigningOlm.selfSigningPkSigning = pkSigning
                             selfSignedKeyIsTrusted = true
                             Timber.i("## CrossSigning - Loading master key success")
                         } else {
@@ -353,6 +347,7 @@ internal class DefaultCrossSigningService @Inject constructor(
      *     │                      │
      *     │                      │
      *     └──▶ USK   ────────────┘
+     * .
      */
     override fun isUserTrusted(otherUserId: String): Boolean {
         return cryptoStore.getCrossSigningInfo(userId)?.isTrusted() == true
@@ -363,7 +358,7 @@ internal class DefaultCrossSigningService @Inject constructor(
     }
 
     /**
-     * Will not force a download of the key, but will verify signatures trust chain
+     * Will not force a download of the key, but will verify signatures trust chain.
      */
     override fun checkUserTrust(otherUserId: String): UserTrustResult {
         Timber.v("## CrossSigning  checkUserTrust for $otherUserId")
@@ -405,7 +400,11 @@ internal class DefaultCrossSigningService @Inject constructor(
 
         // Check that Alice USK signature of Bob MSK is valid
         try {
-            olmUtility!!.verifyEd25519Signature(masterKeySignaturesMadeByMyUserKey, myUserKey.unpaddedBase64PublicKey, otherMasterKey.canonicalSignable())
+            crossSigningOlm.olmUtility.verifyEd25519Signature(
+                    masterKeySignaturesMadeByMyUserKey,
+                    myUserKey.unpaddedBase64PublicKey,
+                    otherMasterKey.canonicalSignable()
+            )
         } catch (failure: Throwable) {
             return UserTrustResult.InvalidSignature(myUserKey, masterKeySignaturesMadeByMyUserKey)
         }
@@ -459,7 +458,7 @@ internal class DefaultCrossSigningService @Inject constructor(
                 if (potentialDevice != null && potentialDevice.isVerified) {
                     // Check signature validity?
                     try {
-                        olmUtility?.verifyEd25519Signature(value, potentialDevice.fingerprint(), myMasterKey.canonicalSignable())
+                        crossSigningOlm.olmUtility.verifyEd25519Signature(value, potentialDevice.fingerprint(), myMasterKey.canonicalSignable())
                         isMaterKeyTrusted = true
                         return@forEach
                     } catch (failure: Throwable) {
@@ -488,7 +487,11 @@ internal class DefaultCrossSigningService @Inject constructor(
 
         // Check that Alice USK signature of Alice MSK is valid
         try {
-            olmUtility!!.verifyEd25519Signature(userKeySignaturesMadeByMyMasterKey, myMasterKey.unpaddedBase64PublicKey, myUserKey.canonicalSignable())
+            crossSigningOlm.olmUtility.verifyEd25519Signature(
+                    userKeySignaturesMadeByMyMasterKey,
+                    myMasterKey.unpaddedBase64PublicKey,
+                    myUserKey.canonicalSignable()
+            )
         } catch (failure: Throwable) {
             return UserTrustResult.InvalidSignature(myUserKey, userKeySignaturesMadeByMyMasterKey)
         }
@@ -507,7 +510,11 @@ internal class DefaultCrossSigningService @Inject constructor(
 
         // Check that Alice USK signature of Alice MSK is valid
         try {
-            olmUtility!!.verifyEd25519Signature(ssKeySignaturesMadeByMyMasterKey, myMasterKey.unpaddedBase64PublicKey, mySSKey.canonicalSignable())
+            crossSigningOlm.olmUtility.verifyEd25519Signature(
+                    ssKeySignaturesMadeByMyMasterKey,
+                    myMasterKey.unpaddedBase64PublicKey,
+                    mySSKey.canonicalSignable()
+            )
         } catch (failure: Throwable) {
             return UserTrustResult.InvalidSignature(mySSKey, ssKeySignaturesMadeByMyMasterKey)
         }
@@ -560,14 +567,16 @@ internal class DefaultCrossSigningService @Inject constructor(
                 return@launch
             }
             val userPubKey = myKeys.userKey()?.unpaddedBase64PublicKey
-            if (userPubKey == null || userPkSigning == null) {
+            if (userPubKey == null || crossSigningOlm.userPkSigning == null) {
                 callback.onFailure(Throwable("## CrossSigning - Cannot sign from this account, privateKeyUnknown $userPubKey"))
                 return@launch
             }
 
             // Sign the other MasterKey with our UserSigning key
-            val newSignature = JsonCanonicalizer.getCanonicalJson(Map::class.java,
-                    otherMasterKeys.signalableJSONDictionary()).let { userPkSigning?.sign(it) }
+            val newSignature = JsonCanonicalizer.getCanonicalJson(
+                    Map::class.java,
+                    otherMasterKeys.signalableJSONDictionary()
+            ).let { crossSigningOlm.userPkSigning?.sign(it) }
 
             if (newSignature == null) {
                 // race??
@@ -614,13 +623,13 @@ internal class DefaultCrossSigningService @Inject constructor(
             }
 
             val ssPubKey = myKeys.selfSigningKey()?.unpaddedBase64PublicKey
-            if (ssPubKey == null || selfSigningPkSigning == null) {
+            if (ssPubKey == null || crossSigningOlm.selfSigningPkSigning == null) {
                 callback.onFailure(Throwable("Cannot sign from this account, public and/or privateKey Unknown $ssPubKey"))
                 return@launch
             }
 
             // Sign with self signing
-            val newSignature = selfSigningPkSigning?.sign(device.canonicalSignable())
+            val newSignature = crossSigningOlm.selfSigningPkSigning?.sign(device.canonicalSignable())
 
             if (newSignature == null) {
                 // race??
@@ -684,7 +693,8 @@ internal class DefaultCrossSigningService @Inject constructor(
         val otherSSKSignature = otherDevice.signatures?.get(otherUserId)?.get("ed25519:${otherKeys.selfSigningKey()?.unpaddedBase64PublicKey}")
                 ?: return legacyFallbackTrust(
                         locallyTrusted,
-                        DeviceTrustResult.MissingDeviceSignature(otherDeviceId, otherKeys.selfSigningKey()
+                        DeviceTrustResult.MissingDeviceSignature(
+                                otherDeviceId, otherKeys.selfSigningKey()
                                 ?.unpaddedBase64PublicKey
                                 ?: ""
                         )
@@ -692,7 +702,11 @@ internal class DefaultCrossSigningService @Inject constructor(
 
         // Check  bob's device is signed by bob's SSK
         try {
-            olmUtility!!.verifyEd25519Signature(otherSSKSignature, otherKeys.selfSigningKey()?.unpaddedBase64PublicKey, otherDevice.canonicalSignable())
+            crossSigningOlm.olmUtility.verifyEd25519Signature(
+                    otherSSKSignature,
+                    otherKeys.selfSigningKey()?.unpaddedBase64PublicKey,
+                    otherDevice.canonicalSignable()
+            )
         } catch (e: Throwable) {
             return legacyFallbackTrust(locallyTrusted, DeviceTrustResult.InvalidDeviceSignature(otherDeviceId, otherSSKSignature, e))
         }
@@ -733,7 +747,8 @@ internal class DefaultCrossSigningService @Inject constructor(
         val otherSSKSignature = otherDevice.signatures?.get(otherKeys.userId)?.get("ed25519:${otherKeys.selfSigningKey()?.unpaddedBase64PublicKey}")
                 ?: return legacyFallbackTrust(
                         locallyTrusted,
-                        DeviceTrustResult.MissingDeviceSignature(otherDevice.deviceId, otherKeys.selfSigningKey()
+                        DeviceTrustResult.MissingDeviceSignature(
+                                otherDevice.deviceId, otherKeys.selfSigningKey()
                                 ?.unpaddedBase64PublicKey
                                 ?: ""
                         )
@@ -741,7 +756,11 @@ internal class DefaultCrossSigningService @Inject constructor(
 
         // Check  bob's device is signed by bob's SSK
         try {
-            olmUtility!!.verifyEd25519Signature(otherSSKSignature, otherKeys.selfSigningKey()?.unpaddedBase64PublicKey, otherDevice.canonicalSignable())
+            crossSigningOlm.olmUtility.verifyEd25519Signature(
+                    otherSSKSignature,
+                    otherKeys.selfSigningKey()?.unpaddedBase64PublicKey,
+                    otherDevice.canonicalSignable()
+            )
         } catch (e: Throwable) {
             return legacyFallbackTrust(locallyTrusted, DeviceTrustResult.InvalidDeviceSignature(otherDevice.deviceId, otherSSKSignature, e))
         }
@@ -781,7 +800,8 @@ internal class DefaultCrossSigningService @Inject constructor(
         // If it's me, recheck trust of all users and devices?
         val users = ArrayList()
         if (otherUserId == userId && currentTrust != trusted) {
-//                reRequestAllPendingRoomKeyRequest()
+            // notify key requester
+            outgoingKeyRequestManager.onSelfCrossSigningTrustChanged(trusted)
             cryptoStore.updateUsersTrust {
                 users.add(it)
                 checkUserTrust(it).isVerified()
@@ -796,19 +816,4 @@ internal class DefaultCrossSigningService @Inject constructor(
             }
         }
     }
-
-//    private fun reRequestAllPendingRoomKeyRequest() {
-//        cryptoCoroutineScope.launch(coroutineDispatchers.crypto) {
-//            Timber.d("## CrossSigning - reRequest pending outgoing room key requests")
-//            cryptoStore.getOutgoingRoomKeyRequests().forEach {
-//                it.requestBody?.let { requestBody ->
-//                    if (cryptoStore.getInboundGroupSession(requestBody.sessionId ?: "", requestBody.senderKey ?: "") == null) {
-//                        outgoingRoomKeyRequestManager.resendRoomKeyRequest(requestBody)
-//                    } else {
-//                        outgoingRoomKeyRequestManager.cancelRoomKeyRequest(requestBody)
-//                    }
-//                }
-//            }
-//        }
-//    }
 }
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/keysbackup/DefaultKeysBackupService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/keysbackup/DefaultKeysBackupService.kt
index e63a6dc791..5ea4695da2 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/keysbackup/DefaultKeysBackupService.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/keysbackup/DefaultKeysBackupService.kt
@@ -54,6 +54,7 @@ import org.matrix.android.sdk.internal.crypto.MXOlmDevice
 import org.matrix.android.sdk.internal.crypto.MegolmSessionData
 import org.matrix.android.sdk.internal.crypto.ObjectSigner
 import org.matrix.android.sdk.internal.crypto.actions.MegolmSessionDataImporter
+import org.matrix.android.sdk.internal.crypto.crosssigning.CrossSigningOlm
 import org.matrix.android.sdk.internal.crypto.keysbackup.model.SignalableMegolmBackupAuthData
 import org.matrix.android.sdk.internal.crypto.keysbackup.model.rest.BackupKeysResult
 import org.matrix.android.sdk.internal.crypto.keysbackup.model.rest.CreateKeysBackupVersionBody
@@ -63,16 +64,11 @@ import org.matrix.android.sdk.internal.crypto.keysbackup.model.rest.RoomKeysBack
 import org.matrix.android.sdk.internal.crypto.keysbackup.model.rest.UpdateKeysBackupVersionBody
 import org.matrix.android.sdk.internal.crypto.keysbackup.tasks.CreateKeysBackupVersionTask
 import org.matrix.android.sdk.internal.crypto.keysbackup.tasks.DeleteBackupTask
-import org.matrix.android.sdk.internal.crypto.keysbackup.tasks.DeleteRoomSessionDataTask
-import org.matrix.android.sdk.internal.crypto.keysbackup.tasks.DeleteRoomSessionsDataTask
-import org.matrix.android.sdk.internal.crypto.keysbackup.tasks.DeleteSessionsDataTask
 import org.matrix.android.sdk.internal.crypto.keysbackup.tasks.GetKeysBackupLastVersionTask
 import org.matrix.android.sdk.internal.crypto.keysbackup.tasks.GetKeysBackupVersionTask
 import org.matrix.android.sdk.internal.crypto.keysbackup.tasks.GetRoomSessionDataTask
 import org.matrix.android.sdk.internal.crypto.keysbackup.tasks.GetRoomSessionsDataTask
 import org.matrix.android.sdk.internal.crypto.keysbackup.tasks.GetSessionsDataTask
-import org.matrix.android.sdk.internal.crypto.keysbackup.tasks.StoreRoomSessionDataTask
-import org.matrix.android.sdk.internal.crypto.keysbackup.tasks.StoreRoomSessionsDataTask
 import org.matrix.android.sdk.internal.crypto.keysbackup.tasks.StoreSessionsDataTask
 import org.matrix.android.sdk.internal.crypto.keysbackup.tasks.UpdateKeysBackupVersionTask
 import org.matrix.android.sdk.internal.crypto.model.OlmInboundGroupSessionWrapper2
@@ -107,21 +103,17 @@ internal class DefaultKeysBackupService @Inject constructor(
         private val cryptoStore: IMXCryptoStore,
         private val olmDevice: MXOlmDevice,
         private val objectSigner: ObjectSigner,
+        private val crossSigningOlm: CrossSigningOlm,
         // Actions
         private val megolmSessionDataImporter: MegolmSessionDataImporter,
         // Tasks
         private val createKeysBackupVersionTask: CreateKeysBackupVersionTask,
         private val deleteBackupTask: DeleteBackupTask,
-        private val deleteRoomSessionDataTask: DeleteRoomSessionDataTask,
-        private val deleteRoomSessionsDataTask: DeleteRoomSessionsDataTask,
-        private val deleteSessionDataTask: DeleteSessionsDataTask,
         private val getKeysBackupLastVersionTask: GetKeysBackupLastVersionTask,
         private val getKeysBackupVersionTask: GetKeysBackupVersionTask,
         private val getRoomSessionDataTask: GetRoomSessionDataTask,
         private val getRoomSessionsDataTask: GetRoomSessionsDataTask,
         private val getSessionsDataTask: GetSessionsDataTask,
-        private val storeRoomSessionDataTask: StoreRoomSessionDataTask,
-        private val storeSessionsDataTask: StoreRoomSessionsDataTask,
         private val storeSessionDataTask: StoreSessionsDataTask,
         private val updateKeysBackupVersionTask: UpdateKeysBackupVersionTask,
         // Task executor
@@ -168,58 +160,76 @@ internal class DefaultKeysBackupService @Inject constructor(
     override fun prepareKeysBackupVersion(password: String?,
                                           progressListener: ProgressListener?,
                                           callback: MatrixCallback) {
-        cryptoCoroutineScope.launch(coroutineDispatchers.main) {
-            runCatching {
-                withContext(coroutineDispatchers.crypto) {
-                    val olmPkDecryption = OlmPkDecryption()
-                    val signalableMegolmBackupAuthData = if (password != null) {
-                        // Generate a private key from the password
-                        val backgroundProgressListener = if (progressListener == null) {
-                            null
-                        } else {
-                            object : ProgressListener {
-                                override fun onProgress(progress: Int, total: Int) {
-                                    uiHandler.post {
-                                        try {
-                                            progressListener.onProgress(progress, total)
-                                        } catch (e: Exception) {
-                                            Timber.e(e, "prepareKeysBackupVersion: onProgress failure")
-                                        }
+        cryptoCoroutineScope.launch(coroutineDispatchers.io) {
+            try {
+                val olmPkDecryption = OlmPkDecryption()
+                val signalableMegolmBackupAuthData = if (password != null) {
+                    // Generate a private key from the password
+                    val backgroundProgressListener = if (progressListener == null) {
+                        null
+                    } else {
+                        object : ProgressListener {
+                            override fun onProgress(progress: Int, total: Int) {
+                                uiHandler.post {
+                                    try {
+                                        progressListener.onProgress(progress, total)
+                                    } catch (e: Exception) {
+                                        Timber.e(e, "prepareKeysBackupVersion: onProgress failure")
                                     }
                                 }
                             }
                         }
-
-                        val generatePrivateKeyResult = generatePrivateKeyWithPassword(password, backgroundProgressListener)
-                        SignalableMegolmBackupAuthData(
-                                publicKey = olmPkDecryption.setPrivateKey(generatePrivateKeyResult.privateKey),
-                                privateKeySalt = generatePrivateKeyResult.salt,
-                                privateKeyIterations = generatePrivateKeyResult.iterations
-                        )
-                    } else {
-                        val publicKey = olmPkDecryption.generateKey()
-
-                        SignalableMegolmBackupAuthData(
-                                publicKey = publicKey
-                        )
                     }
-
-                    val canonicalJson = JsonCanonicalizer.getCanonicalJson(Map::class.java, signalableMegolmBackupAuthData.signalableJSONDictionary())
-
-                    val signedMegolmBackupAuthData = MegolmBackupAuthData(
-                            publicKey = signalableMegolmBackupAuthData.publicKey,
-                            privateKeySalt = signalableMegolmBackupAuthData.privateKeySalt,
-                            privateKeyIterations = signalableMegolmBackupAuthData.privateKeyIterations,
-                            signatures = objectSigner.signObject(canonicalJson)
+                    val generatePrivateKeyResult = generatePrivateKeyWithPassword(password, backgroundProgressListener)
+                    SignalableMegolmBackupAuthData(
+                            publicKey = olmPkDecryption.setPrivateKey(generatePrivateKeyResult.privateKey),
+                            privateKeySalt = generatePrivateKeyResult.salt,
+                            privateKeyIterations = generatePrivateKeyResult.iterations
                     )
-
-                    MegolmBackupCreationInfo(
-                            algorithm = MXCRYPTO_ALGORITHM_MEGOLM_BACKUP,
-                            authData = signedMegolmBackupAuthData,
-                            recoveryKey = computeRecoveryKey(olmPkDecryption.privateKey())
+                } else {
+                    val publicKey = olmPkDecryption.generateKey()
+                    SignalableMegolmBackupAuthData(
+                            publicKey = publicKey
                     )
                 }
-            }.foldToCallback(callback)
+
+                val canonicalJson = JsonCanonicalizer.getCanonicalJson(Map::class.java, signalableMegolmBackupAuthData.signalableJSONDictionary())
+
+                val signatures = mutableMapOf>()
+
+                val deviceSignature = objectSigner.signObject(canonicalJson)
+                deviceSignature.forEach { (userID, content) ->
+                    signatures[userID] = content.toMutableMap()
+                }
+
+                // If we have cross signing add signature, will throw if cross signing not properly configured
+                try {
+                    val crossSign = crossSigningOlm.signObject(CrossSigningOlm.KeyType.MASTER, canonicalJson)
+                    signatures[credentials.userId]?.putAll(crossSign)
+                } catch (failure: Throwable) {
+                    // ignore and log
+                    Timber.w(failure, "prepareKeysBackupVersion: failed to sign with cross signing keys")
+                }
+
+                val signedMegolmBackupAuthData = MegolmBackupAuthData(
+                        publicKey = signalableMegolmBackupAuthData.publicKey,
+                        privateKeySalt = signalableMegolmBackupAuthData.privateKeySalt,
+                        privateKeyIterations = signalableMegolmBackupAuthData.privateKeyIterations,
+                        signatures = signatures
+                )
+                val creationInfo = MegolmBackupCreationInfo(
+                        algorithm = MXCRYPTO_ALGORITHM_MEGOLM_BACKUP,
+                        authData = signedMegolmBackupAuthData,
+                        recoveryKey = computeRecoveryKey(olmPkDecryption.privateKey())
+                )
+                uiHandler.post {
+                    callback.onSuccess(creationInfo)
+                }
+            } catch (failure: Throwable) {
+                uiHandler.post {
+                    callback.onFailure(failure)
+                }
+            }
         }
     }
 
@@ -267,41 +277,39 @@ internal class DefaultKeysBackupService @Inject constructor(
     }
 
     override fun deleteBackup(version: String, callback: MatrixCallback?) {
-        cryptoCoroutineScope.launch(coroutineDispatchers.main) {
-            withContext(coroutineDispatchers.crypto) {
-                // If we're currently backing up to this backup... stop.
-                // (We start using it automatically in createKeysBackupVersion so this is symmetrical).
-                if (keysBackupVersion != null && version == keysBackupVersion?.version) {
-                    resetKeysBackupData()
-                    keysBackupVersion = null
-                    keysBackupStateManager.state = KeysBackupState.Unknown
-                }
+        cryptoCoroutineScope.launch(coroutineDispatchers.io) {
+            // If we're currently backing up to this backup... stop.
+            // (We start using it automatically in createKeysBackupVersion so this is symmetrical).
+            if (keysBackupVersion != null && version == keysBackupVersion?.version) {
+                resetKeysBackupData()
+                keysBackupVersion = null
+                keysBackupStateManager.state = KeysBackupState.Unknown
+            }
 
-                deleteBackupTask
-                        .configureWith(DeleteBackupTask.Params(version)) {
-                            this.callback = object : MatrixCallback {
-                                private fun eventuallyRestartBackup() {
-                                    // Do not stay in KeysBackupState.Unknown but check what is available on the homeserver
-                                    if (state == KeysBackupState.Unknown) {
-                                        checkAndStartKeysBackup()
-                                    }
-                                }
-
-                                override fun onSuccess(data: Unit) {
-                                    eventuallyRestartBackup()
-
-                                    uiHandler.post { callback?.onSuccess(Unit) }
-                                }
-
-                                override fun onFailure(failure: Throwable) {
-                                    eventuallyRestartBackup()
-
-                                    uiHandler.post { callback?.onFailure(failure) }
+            deleteBackupTask
+                    .configureWith(DeleteBackupTask.Params(version)) {
+                        this.callback = object : MatrixCallback {
+                            private fun eventuallyRestartBackup() {
+                                // Do not stay in KeysBackupState.Unknown but check what is available on the homeserver
+                                if (state == KeysBackupState.Unknown) {
+                                    checkAndStartKeysBackup()
                                 }
                             }
+
+                            override fun onSuccess(data: Unit) {
+                                eventuallyRestartBackup()
+
+                                uiHandler.post { callback?.onSuccess(Unit) }
+                            }
+
+                            override fun onFailure(failure: Throwable) {
+                                eventuallyRestartBackup()
+
+                                uiHandler.post { callback?.onFailure(failure) }
+                            }
                         }
-                        .executeBy(taskExecutor)
-            }
+                    }
+                    .executeBy(taskExecutor)
         }
     }
 
@@ -339,6 +347,10 @@ internal class DefaultKeysBackupService @Inject constructor(
 
     override fun backupAllGroupSessions(progressListener: ProgressListener?,
                                         callback: MatrixCallback?) {
+        if (!isEnabled || backupOlmPkEncryption == null || keysBackupVersion == null) {
+            callback?.onFailure(Throwable("Backup not enabled"))
+            return
+        }
         // Get a status right now
         getBackupProgress(object : ProgressListener {
             override fun onProgress(progress: Int, total: Int) {
@@ -427,18 +439,41 @@ internal class DefaultKeysBackupService @Inject constructor(
 
         for ((keyId, mySignature) in mySigs) {
             // XXX: is this how we're supposed to get the device id?
-            var deviceId: String? = null
+            var deviceOrCrossSigningKeyId: String? = null
             val components = keyId.split(":")
             if (components.size == 2) {
-                deviceId = components[1]
+                deviceOrCrossSigningKeyId = components[1]
             }
 
-            if (deviceId != null) {
-                val device = cryptoStore.getUserDevice(userId, deviceId)
+            // Let's check if it's my master key
+            val myMSKPKey = cryptoStore.getMyCrossSigningInfo()?.masterKey()?.unpaddedBase64PublicKey
+            if (deviceOrCrossSigningKeyId == myMSKPKey) {
+                // we have to check if we can trust
+
+                var isSignatureValid = false
+                try {
+                    crossSigningOlm.verifySignature(CrossSigningOlm.KeyType.MASTER, authData.signalableJSONDictionary(), authData.signatures)
+                    isSignatureValid = true
+                } catch (failure: Throwable) {
+                    Timber.w(failure, "getKeysBackupTrust: Bad signature from my user MSK")
+                }
+                val mskTrusted = cryptoStore.getMyCrossSigningInfo()?.masterKey()?.trustLevel?.isVerified() == true
+                if (isSignatureValid && mskTrusted) {
+                    keysBackupVersionTrustIsUsable = true
+                }
+                val signature = KeysBackupVersionTrustSignature.UserSignature(
+                        keyId = deviceOrCrossSigningKeyId,
+                        cryptoCrossSigningKey = cryptoStore.getMyCrossSigningInfo()?.masterKey(),
+                        valid = isSignatureValid
+                )
+
+                keysBackupVersionTrustSignatures.add(signature)
+            } else if (deviceOrCrossSigningKeyId != null) {
+                val device = cryptoStore.getUserDevice(userId, deviceOrCrossSigningKeyId)
                 var isSignatureValid = false
 
                 if (device == null) {
-                    Timber.v("getKeysBackupTrust: Signature from unknown device $deviceId")
+                    Timber.v("getKeysBackupTrust: Signature from unknown device $deviceOrCrossSigningKeyId")
                 } else {
                     val fingerprint = device.fingerprint()
                     if (fingerprint != null) {
@@ -455,8 +490,8 @@ internal class DefaultKeysBackupService @Inject constructor(
                     }
                 }
 
-                val signature = KeysBackupVersionTrustSignature(
-                        deviceId = deviceId,
+                val signature = KeysBackupVersionTrustSignature.DeviceSignature(
+                        deviceId = deviceOrCrossSigningKeyId,
                         device = device,
                         valid = isSignatureValid,
                 )
@@ -480,10 +515,11 @@ internal class DefaultKeysBackupService @Inject constructor(
 
         if (authData == null) {
             Timber.w("trustKeyBackupVersion:trust: Key backup is missing required data")
-
-            callback.onFailure(IllegalArgumentException("Missing element"))
+            uiHandler.post {
+                callback.onFailure(IllegalArgumentException("Missing element"))
+            }
         } else {
-            cryptoCoroutineScope.launch(coroutineDispatchers.main) {
+            cryptoCoroutineScope.launch(coroutineDispatchers.io) {
                 val updateKeysBackupVersionBody = withContext(coroutineDispatchers.crypto) {
                     // Get current signatures, or create an empty set
                     val myUserSignatures = authData.signatures?.get(userId).orEmpty().toMutableMap()
@@ -516,7 +552,8 @@ internal class DefaultKeysBackupService @Inject constructor(
                     UpdateKeysBackupVersionBody(
                             algorithm = keysBackupVersion.algorithm,
                             authData = newMegolmBackupAuthDataWithNewSignature.toJsonDict(),
-                            version = keysBackupVersion.version)
+                            version = keysBackupVersion.version
+                    )
                 }
 
                 // And send it to the homeserver
@@ -535,11 +572,15 @@ internal class DefaultKeysBackupService @Inject constructor(
 
                                     checkAndStartWithKeysBackupVersion(newKeysBackupVersion)
 
-                                    callback.onSuccess(data)
+                                    uiHandler.post {
+                                        callback.onSuccess(data)
+                                    }
                                 }
 
                                 override fun onFailure(failure: Throwable) {
-                                    callback.onFailure(failure)
+                                    uiHandler.post {
+                                        callback.onFailure(failure)
+                                    }
                                 }
                             }
                         }
@@ -553,15 +594,14 @@ internal class DefaultKeysBackupService @Inject constructor(
                                                        callback: MatrixCallback) {
         Timber.v("trustKeysBackupVersionWithRecoveryKey: version ${keysBackupVersion.version}")
 
-        cryptoCoroutineScope.launch(coroutineDispatchers.main) {
-            val isValid = withContext(coroutineDispatchers.crypto) {
-                isValidRecoveryKeyForKeysBackupVersion(recoveryKey, keysBackupVersion)
-            }
+        cryptoCoroutineScope.launch(coroutineDispatchers.io) {
+            val isValid = isValidRecoveryKeyForKeysBackupVersion(recoveryKey, keysBackupVersion)
 
             if (!isValid) {
                 Timber.w("trustKeyBackupVersionWithRecoveryKey: Invalid recovery key.")
-
-                callback.onFailure(IllegalArgumentException("Invalid recovery key or password"))
+                uiHandler.post {
+                    callback.onFailure(IllegalArgumentException("Invalid recovery key or password"))
+                }
             } else {
                 trustKeysBackupVersion(keysBackupVersion, true, callback)
             }
@@ -573,15 +613,14 @@ internal class DefaultKeysBackupService @Inject constructor(
                                                       callback: MatrixCallback) {
         Timber.v("trustKeysBackupVersionWithPassphrase: version ${keysBackupVersion.version}")
 
-        cryptoCoroutineScope.launch(coroutineDispatchers.main) {
-            val recoveryKey = withContext(coroutineDispatchers.crypto) {
-                recoveryKeyFromPassword(password, keysBackupVersion, null)
-            }
+        cryptoCoroutineScope.launch(coroutineDispatchers.io) {
+            val recoveryKey = recoveryKeyFromPassword(password, keysBackupVersion, null)
 
             if (recoveryKey == null) {
                 Timber.w("trustKeysBackupVersionWithPassphrase: Key backup is missing required data")
-
-                callback.onFailure(IllegalArgumentException("Missing element"))
+                uiHandler.post {
+                    callback.onFailure(IllegalArgumentException("Missing element"))
+                }
             } else {
                 // Check trust using the recovery key
                 trustKeysBackupVersionWithRecoveryKey(keysBackupVersion, recoveryKey, callback)
@@ -592,30 +631,28 @@ internal class DefaultKeysBackupService @Inject constructor(
     override fun onSecretKeyGossip(secret: String) {
         Timber.i("## CrossSigning - onSecretKeyGossip")
 
-        cryptoCoroutineScope.launch(coroutineDispatchers.main) {
+        cryptoCoroutineScope.launch(coroutineDispatchers.io) {
             try {
-                when (val keysBackupLastVersionResult = getKeysBackupLastVersionTask.execute(Unit)) {
-                    KeysBackupLastVersionResult.NoKeysBackup  -> {
-                        Timber.d("No keys backup found")
-                    }
-                    is KeysBackupLastVersionResult.KeysBackup -> {
-                        val keysBackupVersion = keysBackupLastVersionResult.keysVersionResult
-                        val recoveryKey = computeRecoveryKey(secret.fromBase64())
-                        if (isValidRecoveryKeyForKeysBackupVersion(recoveryKey, keysBackupVersion)) {
-                            awaitCallback {
-                                trustKeysBackupVersion(keysBackupVersion, true, it)
-                            }
-                            val importResult = awaitCallback {
-                                restoreKeysWithRecoveryKey(keysBackupVersion, recoveryKey, null, null, null, it)
-                            }
-                            withContext(coroutineDispatchers.crypto) {
-                                cryptoStore.saveBackupRecoveryKey(recoveryKey, keysBackupVersion.version)
-                            }
-                            Timber.i("onSecretKeyGossip: Recovered keys $importResult")
-                        } else {
-                            Timber.e("onSecretKeyGossip: Recovery key is not valid ${keysBackupVersion.version}")
+                val keysBackupVersion = getKeysBackupLastVersionTask.execute(Unit).toKeysVersionResult()
+                        ?: return@launch Unit.also {
+                            Timber.d("Failed to get backup last version")
                         }
+                val recoveryKey = computeRecoveryKey(secret.fromBase64())
+                if (isValidRecoveryKeyForKeysBackupVersion(recoveryKey, keysBackupVersion)) {
+                    awaitCallback {
+                        trustKeysBackupVersion(keysBackupVersion, true, it)
                     }
+                    // we don't want to start immediately downloading all as it can take very long
+
+//                    val importResult = awaitCallback {
+//                        restoreKeysWithRecoveryKey(keysBackupVersion, recoveryKey, null, null, null, it)
+//                    }
+                    withContext(coroutineDispatchers.crypto) {
+                        cryptoStore.saveBackupRecoveryKey(recoveryKey, keysBackupVersion.version)
+                    }
+                    Timber.i("onSecretKeyGossip: saved valid backup key")
+                } else {
+                    Timber.e("onSecretKeyGossip: Recovery key is not valid ${keysBackupVersion.version}")
                 }
             } catch (failure: Throwable) {
                 Timber.e("onSecretKeyGossip: failed to trust key backup version ${keysBackupVersion?.version}")
@@ -624,7 +661,7 @@ internal class DefaultKeysBackupService @Inject constructor(
     }
 
     /**
-     * Get public key from a Recovery key
+     * Get public key from a Recovery key.
      *
      * @param recoveryKey the recovery key
      * @return the corresponding public key, from Olm
@@ -678,9 +715,9 @@ internal class DefaultKeysBackupService @Inject constructor(
                                             callback: MatrixCallback) {
         Timber.v("restoreKeysWithRecoveryKey: From backup version: ${keysVersionResult.version}")
 
-        cryptoCoroutineScope.launch(coroutineDispatchers.main) {
+        cryptoCoroutineScope.launch(coroutineDispatchers.io) {
             runCatching {
-                val decryption = withContext(coroutineDispatchers.crypto) {
+                val decryption = withContext(coroutineDispatchers.computation) {
                     // Check if the recovery is valid before going any further
                     if (!isValidRecoveryKeyForKeysBackupVersion(recoveryKey, keysVersionResult)) {
                         Timber.e("restoreKeysWithRecoveryKey: Invalid recovery key for this keys version")
@@ -719,14 +756,18 @@ internal class DefaultKeysBackupService @Inject constructor(
                             }
                         }
                     }
-                    Timber.v("restoreKeysWithRecoveryKey: Decrypted ${sessionsData.size} keys out" +
-                            " of $sessionsFromHsCount from the backup store on the homeserver")
+                    Timber.v(
+                            "restoreKeysWithRecoveryKey: Decrypted ${sessionsData.size} keys out" +
+                                    " of $sessionsFromHsCount from the backup store on the homeserver"
+                    )
 
                     // Do not trigger a backup for them if they come from the backup version we are using
                     val backUp = keysVersionResult.version != keysBackupVersion?.version
                     if (backUp) {
-                        Timber.v("restoreKeysWithRecoveryKey: Those keys will be backed up" +
-                                " to backup version: ${keysBackupVersion?.version}")
+                        Timber.v(
+                                "restoreKeysWithRecoveryKey: Those keys will be backed up" +
+                                        " to backup version: ${keysBackupVersion?.version}"
+                        )
                     }
 
                     // Import them into the crypto store
@@ -749,7 +790,19 @@ internal class DefaultKeysBackupService @Inject constructor(
                     }
                     result
                 }
-            }.foldToCallback(callback)
+            }.foldToCallback(object : MatrixCallback {
+                override fun onSuccess(data: ImportRoomKeysResult) {
+                    uiHandler.post {
+                        callback.onSuccess(data)
+                    }
+                }
+
+                override fun onFailure(failure: Throwable) {
+                    uiHandler.post {
+                        callback.onFailure(failure)
+                    }
+                }
+            })
         }
     }
 
@@ -761,7 +814,7 @@ internal class DefaultKeysBackupService @Inject constructor(
                                               callback: MatrixCallback) {
         Timber.v("[MXKeyBackup] restoreKeyBackup with password: From backup version: ${keysBackupVersion.version}")
 
-        cryptoCoroutineScope.launch(coroutineDispatchers.main) {
+        cryptoCoroutineScope.launch(coroutineDispatchers.io) {
             runCatching {
                 val progressListener = if (stepProgressListener != null) {
                     object : ProgressListener {
@@ -786,13 +839,25 @@ internal class DefaultKeysBackupService @Inject constructor(
                         restoreKeysWithRecoveryKey(keysBackupVersion, recoveryKey, roomId, sessionId, stepProgressListener, it)
                     }
                 }
-            }.foldToCallback(callback)
+            }.foldToCallback(object : MatrixCallback {
+                override fun onSuccess(data: ImportRoomKeysResult) {
+                    uiHandler.post {
+                        callback.onSuccess(data)
+                    }
+                }
+
+                override fun onFailure(failure: Throwable) {
+                    uiHandler.post {
+                        callback.onFailure(failure)
+                    }
+                }
+            })
         }
     }
 
     /**
      * Same method as [RoomKeysRestClient.getRoomKey] except that it accepts nullable
-     * parameters and always returns a KeysBackupData object through the Callback
+     * parameters and always returns a KeysBackupData object through the Callback.
      */
     private suspend fun getKeys(sessionId: String?,
                                 roomId: String?,
@@ -801,19 +866,27 @@ internal class DefaultKeysBackupService @Inject constructor(
             // Get key for the room and for the session
             val data = getRoomSessionDataTask.execute(GetRoomSessionDataTask.Params(roomId, sessionId, version))
             // Convert to KeysBackupData
-            KeysBackupData(mutableMapOf(
-                    roomId to RoomKeysBackupData(mutableMapOf(
-                            sessionId to data
-                    ))
-            ))
+            KeysBackupData(
+                    mutableMapOf(
+                            roomId to RoomKeysBackupData(
+                                    mutableMapOf(
+                                            sessionId to data
+                                    )
+                            )
+                    )
+            )
         } else if (roomId != null) {
             // Get all keys for the room
-            val data = getRoomSessionsDataTask.execute(GetRoomSessionsDataTask.Params(roomId, version))
+            val data = withContext(coroutineDispatchers.io) {
+                getRoomSessionsDataTask.execute(GetRoomSessionsDataTask.Params(roomId, version))
+            }
             // Convert to KeysBackupData
             KeysBackupData(mutableMapOf(roomId to data))
         } else {
             // Get all keys
-            getSessionsDataTask.execute(GetSessionsDataTask.Params(version))
+            withContext(coroutineDispatchers.io) {
+                getSessionsDataTask.execute(GetSessionsDataTask.Params(version))
+            }
         }
     }
 
@@ -838,7 +911,7 @@ internal class DefaultKeysBackupService @Inject constructor(
     }
 
     /**
-     * Do a backup if there are new keys, with a delay
+     * Do a backup if there are new keys, with a delay.
      */
     fun maybeBackupKeys() {
         when {
@@ -1149,7 +1222,7 @@ internal class DefaultKeysBackupService @Inject constructor(
     }
 
     /**
-     * Update the DB with data fetch from the server
+     * Update the DB with data fetch from the server.
      */
     private fun onServerDataRetrieved(count: Int?, etag: String?) {
         cryptoStore.setKeysBackupData(KeysBackupDataEntity()
@@ -1178,7 +1251,7 @@ internal class DefaultKeysBackupService @Inject constructor(
     }
 
     /**
-     * Send a chunk of keys to backup
+     * Send a chunk of keys to backup.
      */
     @UiThread
     private fun backupKeys() {
@@ -1189,7 +1262,6 @@ internal class DefaultKeysBackupService @Inject constructor(
             Timber.v("backupKeys: Invalid configuration")
             backupAllGroupSessionsCallback?.onFailure(IllegalStateException("Invalid configuration"))
             resetBackupAllGroupSessionsListeners()
-
             return
         }
 
@@ -1326,7 +1398,8 @@ internal class DefaultKeysBackupService @Inject constructor(
                 "sender_key" to sessionData.senderKey,
                 "sender_claimed_keys" to sessionData.senderClaimedKeys,
                 "forwarding_curve25519_key_chain" to (sessionData.forwardingCurve25519KeyChain.orEmpty()),
-                "session_key" to sessionData.sessionKey)
+                "session_key" to sessionData.sessionKey
+        )
 
         val json = MoshiProvider.providesMoshi()
                 .adapter(Map::class.java)
@@ -1354,7 +1427,8 @@ internal class DefaultKeysBackupService @Inject constructor(
                 sessionData = mapOf(
                         "ciphertext" to encryptedSessionBackupData.mCipherText,
                         "mac" to encryptedSessionBackupData.mMac,
-                        "ephemeral" to encryptedSessionBackupData.mEphemeralKey)
+                        "ephemeral" to encryptedSessionBackupData.mEphemeralKey
+                )
         )
     }
 
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/keysbackup/KeysBackupPassword.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/keysbackup/KeysBackupPassword.kt
index c12879dbee..d5bab33180 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/keysbackup/KeysBackupPassword.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/keysbackup/KeysBackupPassword.kt
@@ -26,6 +26,7 @@ import java.util.UUID
 import javax.crypto.Mac
 import javax.crypto.spec.SecretKeySpec
 import kotlin.experimental.xor
+import kotlin.system.measureTimeMillis
 
 private const val SALT_LENGTH = 32
 private const val DEFAULT_ITERATION = 500_000
@@ -58,7 +59,7 @@ internal fun generatePrivateKeyWithPassword(password: String,
 }
 
 /**
- * Retrieve a private key from {password, salt, iterations}
+ * Retrieve a private key from {password, salt, iterations}.
  *
  * @param password the password used to generated the private key.
  * @param salt the salt.
@@ -91,57 +92,58 @@ internal fun deriveKey(password: String,
                        iterations: Int,
                        progressListener: ProgressListener?): ByteArray {
     // Note: copied and adapted from MXMegolmExportEncryption
-    val t0 = System.currentTimeMillis()
-
     // based on https://en.wikipedia.org/wiki/PBKDF2 algorithm
     // it is simpler than the generic algorithm because the expected key length is equal to the mac key length.
     // noticed as dklen/hlen
 
-    // dklen = 256
-    // hlen = 512
-    val prf = Mac.getInstance("HmacSHA512")
-
-    prf.init(SecretKeySpec(password.toByteArray(), "HmacSHA512"))
-
     // 256 bits key length
     val dk = ByteArray(32)
-    val uc = ByteArray(64)
 
-    // U1 = PRF(Password, Salt || INT_32_BE(i)) with i goes from 1 to dklen/hlen
-    prf.update(salt.toByteArray())
-    val int32BE = byteArrayOf(0, 0, 0, 1)
-    prf.update(int32BE)
-    prf.doFinal(uc, 0)
+    measureTimeMillis {
+        // dklen = 256
+        // hlen = 512
+        val prf = Mac.getInstance("HmacSHA512")
 
-    // copy to the key
-    System.arraycopy(uc, 0, dk, 0, dk.size)
+        prf.init(SecretKeySpec(password.toByteArray(), "HmacSHA512"))
 
-    var lastProgress = -1
+        val uc = ByteArray(64)
 
-    for (index in 2..iterations) {
-        // Uc = PRF(Password, Uc-1)
-        prf.update(uc)
+        // U1 = PRF(Password, Salt || INT_32_BE(i)) with i goes from 1 to dklen/hlen
+        prf.update(salt.toByteArray())
+        val int32BE = byteArrayOf(0, 0, 0, 1)
+        prf.update(int32BE)
         prf.doFinal(uc, 0)
 
-        // F(Password, Salt, c, i) = U1 ^ U2 ^ ... ^ Uc
-        for (byteIndex in dk.indices) {
-            dk[byteIndex] = dk[byteIndex] xor uc[byteIndex]
-        }
+        // copy to the key
+        System.arraycopy(uc, 0, dk, 0, dk.size)
 
-        val progress = (index + 1) * 100 / iterations
-        if (progress != lastProgress) {
-            lastProgress = progress
-            progressListener?.onProgress(lastProgress, 100)
+        var lastProgress = -1
+
+        for (index in 2..iterations) {
+            // Uc = PRF(Password, Uc-1)
+            prf.update(uc)
+            prf.doFinal(uc, 0)
+
+            // F(Password, Salt, c, i) = U1 ^ U2 ^ ... ^ Uc
+            for (byteIndex in dk.indices) {
+                dk[byteIndex] = dk[byteIndex] xor uc[byteIndex]
+            }
+
+            val progress = (index + 1) * 100 / iterations
+            if (progress != lastProgress) {
+                lastProgress = progress
+                progressListener?.onProgress(lastProgress, 100)
+            }
         }
+    }.also {
+        Timber.v("KeysBackupPassword: deriveKeys() : $iterations in $it ms")
     }
 
-    Timber.v("KeysBackupPassword: deriveKeys() : " + iterations + " in " + (System.currentTimeMillis() - t0) + " ms")
-
     return dk
 }
 
 /**
- * Generate a 32 chars salt
+ * Generate a 32 chars salt.
  */
 private fun generateSalt(): String {
     val salt = buildString {
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/keysbackup/api/RoomKeysApi.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/keysbackup/api/RoomKeysApi.kt
index 8464b33526..ea23be5923 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/keysbackup/api/RoomKeysApi.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/keysbackup/api/RoomKeysApi.kt
@@ -50,7 +50,7 @@ internal interface RoomKeysApi {
     suspend fun createKeysBackupVersion(@Body createKeysBackupVersionBody: CreateKeysBackupVersionBody): KeysVersion
 
     /**
-     * Get the key backup last version
+     * Get the key backup last version.
      * If not supported by the server, an error is returned: {"errcode":"M_NOT_FOUND","error":"No backup found"}
      */
     @GET(NetworkConstants.URI_API_PREFIX_PATH_UNSTABLE + "room_keys/version")
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/keysbackup/model/rest/CreateKeysBackupVersionBody.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/keysbackup/model/rest/CreateKeysBackupVersionBody.kt
index a6bd8f8aaa..2d483893c0 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/keysbackup/model/rest/CreateKeysBackupVersionBody.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/keysbackup/model/rest/CreateKeysBackupVersionBody.kt
@@ -23,13 +23,13 @@ import org.matrix.android.sdk.api.util.JsonDict
 @JsonClass(generateAdapter = true)
 internal data class CreateKeysBackupVersionBody(
         /**
-         * The algorithm used for storing backups. Currently, only "m.megolm_backup.v1.curve25519-aes-sha2" is defined
+         * The algorithm used for storing backups. Currently, only "m.megolm_backup.v1.curve25519-aes-sha2" is defined.
          */
         @Json(name = "algorithm")
         override val algorithm: String,
 
         /**
-         * algorithm-dependent data, for "m.megolm_backup.v1.curve25519-aes-sha2"
+         * algorithm-dependent data, for "m.megolm_backup.v1.curve25519-aes-sha2".
          * see [org.matrix.android.sdk.internal.crypto.keysbackup.MegolmBackupAuthData]
          */
         @Json(name = "auth_data")
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/keysbackup/model/rest/KeysAlgorithmAndData.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/keysbackup/model/rest/KeysAlgorithmAndData.kt
index 898b357c51..19581a686b 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/keysbackup/model/rest/KeysAlgorithmAndData.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/keysbackup/model/rest/KeysAlgorithmAndData.kt
@@ -41,17 +41,17 @@ import org.matrix.android.sdk.internal.di.MoshiProvider
 internal interface KeysAlgorithmAndData {
 
     /**
-     * The algorithm used for storing backups. Currently, only "m.megolm_backup.v1.curve25519-aes-sha2" is defined
+     * The algorithm used for storing backups. Currently, only "m.megolm_backup.v1.curve25519-aes-sha2" is defined.
      */
     val algorithm: String
 
     /**
-     * algorithm-dependent data, for "m.megolm_backup.v1.curve25519-aes-sha2" see [org.matrix.android.sdk.internal.crypto.keysbackup.MegolmBackupAuthData]
+     * algorithm-dependent data, for "m.megolm_backup.v1.curve25519-aes-sha2" see [org.matrix.android.sdk.internal.crypto.keysbackup.MegolmBackupAuthData].
      */
     val authData: JsonDict
 
     /**
-     * Facility method to convert authData to a MegolmBackupAuthData object
+     * Facility method to convert authData to a MegolmBackupAuthData object.
      */
     fun getAuthDataAsMegolmBackupAuthData(): MegolmBackupAuthData? {
         return MoshiProvider.providesMoshi()
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/keysbackup/model/rest/UpdateKeysBackupVersionBody.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/keysbackup/model/rest/UpdateKeysBackupVersionBody.kt
index 3f2def84d5..a4b8f811e4 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/keysbackup/model/rest/UpdateKeysBackupVersionBody.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/keysbackup/model/rest/UpdateKeysBackupVersionBody.kt
@@ -29,7 +29,7 @@ internal data class UpdateKeysBackupVersionBody(
         override val algorithm: String,
 
         /**
-         * algorithm-dependent data, for "m.megolm_backup.v1.curve25519-aes-sha2"
+         * algorithm-dependent data, for "m.megolm_backup.v1.curve25519-aes-sha2".
          * see [org.matrix.android.sdk.internal.crypto.keysbackup.MegolmBackupAuthData]
          */
         @Json(name = "auth_data")
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/keysbackup/tasks/DeleteRoomSessionDataTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/keysbackup/tasks/DeleteRoomSessionDataTask.kt
index 7f1b03b932..f90522b036 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/keysbackup/tasks/DeleteRoomSessionDataTask.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/keysbackup/tasks/DeleteRoomSessionDataTask.kt
@@ -40,7 +40,8 @@ internal class DefaultDeleteRoomSessionDataTask @Inject constructor(
             roomKeysApi.deleteRoomSessionData(
                     params.roomId,
                     params.sessionId,
-                    params.version)
+                    params.version
+            )
         }
     }
 }
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/keysbackup/tasks/DeleteRoomSessionsDataTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/keysbackup/tasks/DeleteRoomSessionsDataTask.kt
index 394cc861d6..7db33e0c46 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/keysbackup/tasks/DeleteRoomSessionsDataTask.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/keysbackup/tasks/DeleteRoomSessionsDataTask.kt
@@ -38,7 +38,8 @@ internal class DefaultDeleteRoomSessionsDataTask @Inject constructor(
         return executeRequest(globalErrorReceiver) {
             roomKeysApi.deleteRoomSessionsData(
                     params.roomId,
-                    params.version)
+                    params.version
+            )
         }
     }
 }
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/keysbackup/tasks/GetRoomSessionDataTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/keysbackup/tasks/GetRoomSessionDataTask.kt
index ff515ed80f..df8e6bfebf 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/keysbackup/tasks/GetRoomSessionDataTask.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/keysbackup/tasks/GetRoomSessionDataTask.kt
@@ -41,7 +41,8 @@ internal class DefaultGetRoomSessionDataTask @Inject constructor(
             roomKeysApi.getRoomSessionData(
                     params.roomId,
                     params.sessionId,
-                    params.version)
+                    params.version
+            )
         }
     }
 }
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/keysbackup/tasks/GetRoomSessionsDataTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/keysbackup/tasks/GetRoomSessionsDataTask.kt
index 1b4fe2d966..f9ea078ef2 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/keysbackup/tasks/GetRoomSessionsDataTask.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/keysbackup/tasks/GetRoomSessionsDataTask.kt
@@ -39,7 +39,8 @@ internal class DefaultGetRoomSessionsDataTask @Inject constructor(
         return executeRequest(globalErrorReceiver) {
             roomKeysApi.getRoomSessionsData(
                     params.roomId,
-                    params.version)
+                    params.version
+            )
         }
     }
 }
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/keysbackup/tasks/StoreRoomSessionDataTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/keysbackup/tasks/StoreRoomSessionDataTask.kt
index 180aaecf82..637c6ddeb7 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/keysbackup/tasks/StoreRoomSessionDataTask.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/keysbackup/tasks/StoreRoomSessionDataTask.kt
@@ -44,7 +44,8 @@ internal class DefaultStoreRoomSessionDataTask @Inject constructor(
                     params.roomId,
                     params.sessionId,
                     params.version,
-                    params.keyBackupData)
+                    params.keyBackupData
+            )
         }
     }
 }
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/keysbackup/tasks/StoreRoomSessionsDataTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/keysbackup/tasks/StoreRoomSessionsDataTask.kt
index d1aa9d2eb0..09e52da755 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/keysbackup/tasks/StoreRoomSessionsDataTask.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/keysbackup/tasks/StoreRoomSessionsDataTask.kt
@@ -42,7 +42,8 @@ internal class DefaultStoreRoomSessionsDataTask @Inject constructor(
             roomKeysApi.storeRoomSessionsData(
                     params.roomId,
                     params.version,
-                    params.roomKeysBackupData)
+                    params.roomKeysBackupData
+            )
         }
     }
 }
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/keysbackup/tasks/StoreSessionsDataTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/keysbackup/tasks/StoreSessionsDataTask.kt
index 3dbeafe9de..47f2578c43 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/keysbackup/tasks/StoreSessionsDataTask.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/keysbackup/tasks/StoreSessionsDataTask.kt
@@ -40,7 +40,8 @@ internal class DefaultStoreSessionsDataTask @Inject constructor(
         return executeRequest(globalErrorReceiver) {
             roomKeysApi.storeSessionsData(
                     params.version,
-                    params.keysBackupData)
+                    params.keysBackupData
+            )
         }
     }
 }
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/keysbackup/util/Base58.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/keysbackup/util/Base58.kt
index 0e746f289b..407e034acb 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/keysbackup/util/Base58.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/keysbackup/util/Base58.kt
@@ -39,7 +39,7 @@ private const val ALPHABET = "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqr
 private val BASE = BigInteger.valueOf(58)
 
 /**
- * Encode a byte array to a human readable string with base58 chars
+ * Encode a byte array to a human readable string with base58 chars.
  */
 internal fun base58encode(input: ByteArray): String {
     var bi = BigInteger(1, input)
@@ -62,7 +62,7 @@ internal fun base58encode(input: ByteArray): String {
 }
 
 /**
- * Decode a base58 String to a byte array
+ * Decode a base58 String to a byte array.
  */
 internal fun base58decode(input: String): ByteArray {
     var result = decodeToBigInteger(input).toByteArray()
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/model/MXKey.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/model/MXKey.kt
index cff713bf8f..6bfa56ae8d 100755
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/model/MXKey.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/model/MXKey.kt
@@ -36,13 +36,13 @@ internal data class MXKey(
         val value: String,
 
         /**
-         * signature user Id to [deviceid][signature]
+         * signature user Id to [deviceid][signature].
          */
         private val signatures: Map>,
 
         /**
          * We have to store the original json because it can contain other fields
-         * that we don't support yet but they would be needed to check signatures
+         * that we don't support yet but they would be needed to check signatures.
          */
         private val rawMap: JsonDict
 ) {
@@ -57,7 +57,7 @@ internal data class MXKey(
     }
 
     /**
-     * Returns a signature for an user Id and a signkey
+     * Returns a signature for an user Id and a signkey.
      *
      * @param userId  the user id
      * @param signkey the sign key
@@ -81,7 +81,7 @@ internal data class MXKey(
         // const val KEY_ED_25519_TYPE = "ed25519"
 
         /**
-         * Convert a map to a MXKey
+         * Convert a map to a MXKey.
          *
          * @param map the map to convert
          *
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/model/MXOlmSessionResult.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/model/MXOlmSessionResult.kt
index 671827799e..666ab2d678 100755
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/model/MXOlmSessionResult.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/model/MXOlmSessionResult.kt
@@ -21,7 +21,7 @@ import java.io.Serializable
 
 internal data class MXOlmSessionResult(
         /**
-         * the device
+         * the device.
          */
         val deviceInfo: CryptoDeviceInfo,
         /**
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/model/OlmInboundGroupSessionWrapper.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/model/OlmInboundGroupSessionWrapper.kt
index 45ffcc6606..ecb2946680 100755
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/model/OlmInboundGroupSessionWrapper.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/model/OlmInboundGroupSessionWrapper.kt
@@ -60,7 +60,7 @@ internal class OlmInboundGroupSessionWrapper : Serializable {
         }
 
     /**
-     * Constructor
+     * Constructor.
      *
      * @param sessionKey the session key
      * @param isImported true if it is an imported session key
@@ -101,7 +101,7 @@ internal class OlmInboundGroupSessionWrapper : Serializable {
     }
 
     /**
-     * Export the inbound group session keys
+     * Export the inbound group session keys.
      *
      * @return the inbound group session as MegolmSessionData if the operation succeeds
      */
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/model/OlmInboundGroupSessionWrapper2.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/model/OlmInboundGroupSessionWrapper2.kt
index 1f671aa896..289c169d6d 100755
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/model/OlmInboundGroupSessionWrapper2.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/model/OlmInboundGroupSessionWrapper2.kt
@@ -57,7 +57,7 @@ internal class OlmInboundGroupSessionWrapper2 : Serializable {
         }
 
     /**
-     * Constructor
+     * Constructor.
      *
      * @param sessionKey the session key
      * @param isImported true if it is an imported session key
@@ -104,7 +104,7 @@ internal class OlmInboundGroupSessionWrapper2 : Serializable {
     }
 
     /**
-     * Export the inbound group session keys
+     * Export the inbound group session keys.
      * @param index the index to export. If null, the first known index will be used
      *
      * @return the inbound group session as MegolmSessionData if the operation succeeds
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/model/OlmSessionWrapper.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/model/OlmSessionWrapper.kt
index 927d049eca..a1e58ead0c 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/model/OlmSessionWrapper.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/model/OlmSessionWrapper.kt
@@ -20,7 +20,7 @@ import kotlinx.coroutines.sync.Mutex
 import org.matrix.olm.OlmSession
 
 /**
- * Encapsulate a OlmSession and a last received message Timestamp
+ * Encapsulate a OlmSession and a last received message Timestamp.
  */
 internal data class OlmSessionWrapper(
         // The associated olm session.
@@ -32,9 +32,9 @@ internal data class OlmSessionWrapper(
 ) {
 
     /**
-     * Notify that a message has been received on this olm session so that it updates `lastReceivedMessageTs`
+     * Notify that a message has been received on this olm session so that it updates `lastReceivedMessageTs`.
      */
-    fun onMessageReceived() {
-        lastReceivedMessageTs = System.currentTimeMillis()
+    fun onMessageReceived(currentTimeMillis: Long) {
+        lastReceivedMessageTs = currentTimeMillis
     }
 }
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/model/rest/DeleteDeviceParams.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/model/rest/DeleteDeviceParams.kt
index f636ab890d..c26c6107c4 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/model/rest/DeleteDeviceParams.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/model/rest/DeleteDeviceParams.kt
@@ -19,7 +19,7 @@ import com.squareup.moshi.Json
 import com.squareup.moshi.JsonClass
 
 /**
- * This class provides the parameter to delete a device
+ * This class provides the parameter to delete a device.
  */
 @JsonClass(generateAdapter = true)
 internal data class DeleteDeviceParams(
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/model/rest/KeyChangesResponse.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/model/rest/KeyChangesResponse.kt
index f0ed77a1e3..5d432eca8c 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/model/rest/KeyChangesResponse.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/model/rest/KeyChangesResponse.kt
@@ -19,7 +19,7 @@ import com.squareup.moshi.Json
 import com.squareup.moshi.JsonClass
 
 /**
- * This class describes the key changes response
+ * This class describes the key changes response.
  */
 @JsonClass(generateAdapter = true)
 internal data class KeyChangesResponse(
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/model/rest/KeyVerificationAccept.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/model/rest/KeyVerificationAccept.kt
index 7a5773bf24..9fdeda01c9 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/model/rest/KeyVerificationAccept.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/model/rest/KeyVerificationAccept.kt
@@ -35,25 +35,25 @@ internal data class KeyVerificationAccept(
         override val transactionId: String? = null,
 
         /**
-         * The key agreement protocol that Bob’s device has selected to use, out of the list proposed by Alice’s device
+         * The key agreement protocol that Bob’s device has selected to use, out of the list proposed by Alice’s device.
          */
         @Json(name = "key_agreement_protocol")
         override val keyAgreementProtocol: String? = null,
 
         /**
-         * The hash algorithm that Bob’s device has selected to use, out of the list proposed by Alice’s device
+         * The hash algorithm that Bob’s device has selected to use, out of the list proposed by Alice’s device.
          */
         @Json(name = "hash")
         override val hash: String? = null,
 
         /**
-         * The message authentication code that Bob’s device has selected to use, out of the list proposed by Alice’s device
+         * The message authentication code that Bob’s device has selected to use, out of the list proposed by Alice’s device.
          */
         @Json(name = "message_authentication_code")
         override val messageAuthenticationCode: String? = null,
 
         /**
-         * An array of short authentication string methods that Bob’s client (and Bob) understands.  Must be a subset of the list proposed by Alice’s device
+         * An array of short authentication string methods that Bob’s client (and Bob) understands.  Must be a subset of the list proposed by Alice’s device.
          */
         @Json(name = "short_authentication_string")
         override val shortAuthenticationStrings: List? = null,
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/model/rest/KeyVerificationCancel.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/model/rest/KeyVerificationCancel.kt
index 90272bf0e4..2858ef3eed 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/model/rest/KeyVerificationCancel.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/model/rest/KeyVerificationCancel.kt
@@ -27,18 +27,18 @@ import org.matrix.android.sdk.internal.crypto.verification.VerificationInfoCance
 @JsonClass(generateAdapter = true)
 internal data class KeyVerificationCancel(
         /**
-         * the transaction ID of the verification to cancel
+         * the transaction ID of the verification to cancel.
          */
         @Json(name = "transaction_id")
         override val transactionId: String? = null,
 
         /**
-         * machine-readable reason for cancelling, see #CancelCode
+         * machine-readable reason for cancelling, see #CancelCode.
          */
         override val code: String? = null,
 
         /**
-         * human-readable reason for cancelling.  This should only be used if the receiving client does not understand the code given.
+         * human-readable reason for cancelling. This should only be used if the receiving client does not understand the code given.
          */
         override val reason: String? = null
 ) : SendToDeviceObject, VerificationInfoCancel {
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/model/rest/KeyVerificationKey.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/model/rest/KeyVerificationKey.kt
index 19d8c32ddf..a833148b9d 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/model/rest/KeyVerificationKey.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/model/rest/KeyVerificationKey.kt
@@ -27,12 +27,12 @@ import org.matrix.android.sdk.internal.crypto.verification.VerificationInfoKeyFa
 @JsonClass(generateAdapter = true)
 internal data class KeyVerificationKey(
         /**
-         * the ID of the transaction that the message is part of
+         * The ID of the transaction that the message is part of.
          */
         @Json(name = "transaction_id") override val transactionId: String? = null,
 
         /**
-         * The device’s ephemeral public key, as an unpadded base64 string
+         * The device’s ephemeral public key, as an unpadded base64 string.
          */
         @Json(name = "key") override val key: String? = null
 
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/model/rest/KeysQueryBody.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/model/rest/KeysQueryBody.kt
index 4f98be9da3..dc570820f2 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/model/rest/KeysQueryBody.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/model/rest/KeysQueryBody.kt
@@ -20,7 +20,7 @@ import com.squareup.moshi.Json
 import com.squareup.moshi.JsonClass
 
 /**
- * This class represents the body to /keys/query
+ * This class represents the body to /keys/query.
  */
 @JsonClass(generateAdapter = true)
 internal data class KeysQueryBody(
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/model/rest/KeysUploadResponse.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/model/rest/KeysUploadResponse.kt
index 3d0ea8677f..c445f55ba7 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/model/rest/KeysUploadResponse.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/model/rest/KeysUploadResponse.kt
@@ -31,7 +31,7 @@ internal data class KeysUploadResponse(
         val oneTimeKeyCounts: Map? = null
 ) {
     /**
-     * Helper methods to extract information from 'oneTimeKeyCounts'
+     * Helper methods to extract information from 'oneTimeKeyCounts'.
      *
      * @param algorithm the expected algorithm
      * @return the time key counts
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/model/rest/RestKeyInfo.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/model/rest/RestKeyInfo.kt
index 66247d07d1..cdcbe5ffe2 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/model/rest/RestKeyInfo.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/model/rest/RestKeyInfo.kt
@@ -23,7 +23,7 @@ import org.matrix.android.sdk.internal.crypto.model.CryptoInfoMapper
 @JsonClass(generateAdapter = true)
 internal data class RestKeyInfo(
         /**
-         * The user who owns the key
+         * The user who owns the key.
          */
         @Json(name = "user_id")
         val userId: String,
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/model/rest/ShareRequestCancellation.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/model/rest/ShareRequestCancellation.kt
index a96534fc3a..ea9b451ed6 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/model/rest/ShareRequestCancellation.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/model/rest/ShareRequestCancellation.kt
@@ -21,7 +21,7 @@ import org.matrix.android.sdk.api.session.crypto.model.GossipingToDeviceObject
 import org.matrix.android.sdk.api.session.crypto.model.GossipingToDeviceObject.Companion.ACTION_SHARE_CANCELLATION
 
 /**
- * Class representing a room key request cancellation content
+ * Class representing a room key request cancellation content.
  */
 @JsonClass(generateAdapter = true)
 internal data class ShareRequestCancellation(
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/model/rest/SignatureUploadResponse.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/model/rest/SignatureUploadResponse.kt
index fb92b67fc4..f89426d91b 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/model/rest/SignatureUploadResponse.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/model/rest/SignatureUploadResponse.kt
@@ -20,7 +20,7 @@ import com.squareup.moshi.Json
 import com.squareup.moshi.JsonClass
 
 /**
- * Upload Signature response
+ * Upload Signature response.
  */
 @JsonClass(generateAdapter = true)
 internal data class SignatureUploadResponse(
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/model/rest/UploadSignatureQueryBuilder.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/model/rest/UploadSignatureQueryBuilder.kt
index dd0ce47cd3..1c1e223bc8 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/model/rest/UploadSignatureQueryBuilder.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/model/rest/UploadSignatureQueryBuilder.kt
@@ -21,7 +21,7 @@ import org.matrix.android.sdk.api.session.crypto.model.CryptoDeviceInfo
 import org.matrix.android.sdk.internal.crypto.model.toRest
 
 /**
- * Helper class to build CryptoApi#uploadSignatures params
+ * Helper class to build CryptoApi#uploadSignatures params.
  */
 internal data class UploadSignatureQueryBuilder(
         private val deviceInfoList: MutableList = mutableListOf(),
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/repository/WarnOnUnknownDeviceRepository.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/repository/WarnOnUnknownDeviceRepository.kt
index deec8b1b3c..d699950e4f 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/repository/WarnOnUnknownDeviceRepository.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/repository/WarnOnUnknownDeviceRepository.kt
@@ -22,7 +22,7 @@ import javax.inject.Inject
 @SessionScope
 internal class WarnOnUnknownDeviceRepository @Inject constructor() {
 
-    // TODO: set it back to true by default. Need UI
+    // TODO set it back to true by default. Need UI
     // Warn the user if some new devices are detected while encrypting a message.
     private var warnOnUnknownDevices = false
 
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/secrets/DefaultSharedSecretStorageService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/secrets/DefaultSharedSecretStorageService.kt
index 19e66635c7..1ded9c6c7e 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/secrets/DefaultSharedSecretStorageService.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/secrets/DefaultSharedSecretStorageService.kt
@@ -40,7 +40,7 @@ import org.matrix.android.sdk.api.session.securestorage.SsssKeySpec
 import org.matrix.android.sdk.api.session.securestorage.SsssPassphrase
 import org.matrix.android.sdk.api.util.fromBase64
 import org.matrix.android.sdk.api.util.toBase64NoPadding
-import org.matrix.android.sdk.internal.crypto.OutgoingGossipingRequestManager
+import org.matrix.android.sdk.internal.crypto.SecretShareManager
 import org.matrix.android.sdk.internal.crypto.keysbackup.generatePrivateKeyWithPassword
 import org.matrix.android.sdk.internal.crypto.tools.HkdfSha256
 import org.matrix.android.sdk.internal.crypto.tools.withOlmDecryption
@@ -57,7 +57,7 @@ import kotlin.experimental.and
 internal class DefaultSharedSecretStorageService @Inject constructor(
         @UserId private val userId: String,
         private val accountDataService: SessionAccountDataService,
-        private val outgoingGossipingRequestManager: OutgoingGossipingRequestManager,
+        private val secretShareManager: SecretShareManager,
         private val coroutineDispatchers: MatrixCoroutineDispatchers,
         private val cryptoCoroutineScope: CoroutineScope
 ) : SharedSecretStorageService {
@@ -66,7 +66,7 @@ internal class DefaultSharedSecretStorageService @Inject constructor(
                                      key: SsssKeySpec?,
                                      keyName: String,
                                      keySigner: KeySigner?): SsssKeyCreationInfo {
-        return withContext(cryptoCoroutineScope.coroutineContext + coroutineDispatchers.main) {
+        return withContext(cryptoCoroutineScope.coroutineContext + coroutineDispatchers.computation) {
             val bytes = (key as? RawBytesKeySpec)?.privateKey
                     ?: ByteArray(32).also {
                         SecureRandom().nextBytes(it)
@@ -99,7 +99,7 @@ internal class DefaultSharedSecretStorageService @Inject constructor(
                                                    passphrase: String,
                                                    keySigner: KeySigner,
                                                    progressListener: ProgressListener?): SsssKeyCreationInfo {
-        return withContext(cryptoCoroutineScope.coroutineContext + coroutineDispatchers.main) {
+        return withContext(cryptoCoroutineScope.coroutineContext + coroutineDispatchers.computation) {
             val privatePart = generatePrivateKeyWithPassword(passphrase, progressListener)
 
             val storageKeyContent = SecretStorageKeyContent(
@@ -158,7 +158,7 @@ internal class DefaultSharedSecretStorageService @Inject constructor(
     }
 
     override suspend fun storeSecret(name: String, secretBase64: String, keys: List) {
-        withContext(cryptoCoroutineScope.coroutineContext + coroutineDispatchers.main) {
+        withContext(cryptoCoroutineScope.coroutineContext + coroutineDispatchers.computation) {
             val encryptedContents = HashMap()
             keys.forEach {
                 val keyId = it.keyId
@@ -174,7 +174,7 @@ internal class DefaultSharedSecretStorageService @Inject constructor(
                             throw SharedSecretStorageError.UnknownAlgorithm(key.keyInfo.content.algorithm ?: "")
                         }
                     }
-                    is KeyInfoResult.Error -> throw key.error
+                    is KeyInfoResult.Error   -> throw key.error
                 }
             }
 
@@ -213,7 +213,8 @@ internal class DefaultSharedSecretStorageService @Inject constructor(
                 secretKey.privateKey,
                 ByteArray(32) { 0.toByte() },
                 secretName.toByteArray(),
-                64)
+                64
+        )
 
         // The first 32 bytes are used as the AES key, and the next 32 bytes are used as the MAC key
         val aesKey = pseudoRandomKey.copyOfRange(0, 32)
@@ -255,7 +256,8 @@ internal class DefaultSharedSecretStorageService @Inject constructor(
                 secretKey.privateKey,
                 ByteArray(32) { 0.toByte() },
                 secretName.toByteArray(),
-                64)
+                64
+        )
 
         // The first 32 bytes are used as the AES key, and the next 32 bytes are used as the MAC key
         val aesKey = pseudoRandomKey.copyOfRange(0, 32)
@@ -316,7 +318,7 @@ internal class DefaultSharedSecretStorageService @Inject constructor(
         val algorithm = key.keyInfo.content
         if (SSSS_ALGORITHM_CURVE25519_AES_SHA2 == algorithm.algorithm) {
             val keySpec = secretKey as? RawBytesKeySpec ?: throw SharedSecretStorageError.BadKeyFormat
-            return withContext(cryptoCoroutineScope.coroutineContext + coroutineDispatchers.main) {
+            return withContext(cryptoCoroutineScope.coroutineContext + coroutineDispatchers.computation) {
                 // decrypt from recovery key
                 withOlmDecryption { olmPkDecryption ->
                     olmPkDecryption.setPrivateKey(keySpec.privateKey)
@@ -331,7 +333,7 @@ internal class DefaultSharedSecretStorageService @Inject constructor(
             }
         } else if (SSSS_ALGORITHM_AES_HMAC_SHA2 == algorithm.algorithm) {
             val keySpec = secretKey as? RawBytesKeySpec ?: throw SharedSecretStorageError.BadKeyFormat
-            return withContext(cryptoCoroutineScope.coroutineContext + coroutineDispatchers.main) {
+            return withContext(cryptoCoroutineScope.coroutineContext + coroutineDispatchers.computation) {
                 decryptAesHmacSha2(keySpec, name, secretContent)
             }
         } else {
@@ -378,10 +380,7 @@ internal class DefaultSharedSecretStorageService @Inject constructor(
         return IntegrityResult.Success(keyInfo.content.passphrase != null)
     }
 
-    override fun requestSecret(name: String, myOtherDeviceId: String) {
-        outgoingGossipingRequestManager.sendSecretShareRequest(
-                name,
-                mapOf(userId to listOf(myOtherDeviceId))
-        )
+    override suspend fun requestSecret(name: String, myOtherDeviceId: String) {
+        secretShareManager.requestSecretTo(myOtherDeviceId, name)
     }
 }
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/IMXCryptoStore.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/IMXCryptoStore.kt
index 8bedb78808..480009dbce 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/IMXCryptoStore.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/IMXCryptoStore.kt
@@ -19,23 +19,22 @@ package org.matrix.android.sdk.internal.crypto.store
 import androidx.lifecycle.LiveData
 import androidx.paging.PagedList
 import org.matrix.android.sdk.api.session.crypto.NewSessionListener
+import org.matrix.android.sdk.api.session.crypto.OutgoingKeyRequest
+import org.matrix.android.sdk.api.session.crypto.OutgoingRoomKeyRequestState
 import org.matrix.android.sdk.api.session.crypto.crosssigning.CryptoCrossSigningKey
 import org.matrix.android.sdk.api.session.crypto.crosssigning.MXCrossSigningInfo
 import org.matrix.android.sdk.api.session.crypto.crosssigning.PrivateKeysInfo
 import org.matrix.android.sdk.api.session.crypto.keysbackup.SavedKeyBackupKeyInfo
+import org.matrix.android.sdk.api.session.crypto.model.AuditTrail
 import org.matrix.android.sdk.api.session.crypto.model.CryptoDeviceInfo
 import org.matrix.android.sdk.api.session.crypto.model.DeviceInfo
-import org.matrix.android.sdk.api.session.crypto.model.GossipingRequestState
-import org.matrix.android.sdk.api.session.crypto.model.IncomingRoomKeyRequest
 import org.matrix.android.sdk.api.session.crypto.model.MXUsersDevicesMap
-import org.matrix.android.sdk.api.session.crypto.model.OutgoingGossipingRequestState
-import org.matrix.android.sdk.api.session.crypto.model.OutgoingRoomKeyRequest
 import org.matrix.android.sdk.api.session.crypto.model.RoomKeyRequestBody
+import org.matrix.android.sdk.api.session.crypto.model.TrailType
 import org.matrix.android.sdk.api.session.events.model.Event
 import org.matrix.android.sdk.api.session.events.model.content.RoomKeyWithHeldContent
+import org.matrix.android.sdk.api.session.events.model.content.WithHeldCode
 import org.matrix.android.sdk.api.util.Optional
-import org.matrix.android.sdk.internal.crypto.IncomingShareRequestCommon
-import org.matrix.android.sdk.internal.crypto.OutgoingSecretRequest
 import org.matrix.android.sdk.internal.crypto.model.OlmInboundGroupSessionWrapper2
 import org.matrix.android.sdk.internal.crypto.model.OlmSessionWrapper
 import org.matrix.android.sdk.internal.crypto.model.OutboundGroupSessionWrapper
@@ -44,7 +43,7 @@ import org.matrix.olm.OlmAccount
 import org.matrix.olm.OlmOutboundGroupSession
 
 /**
- * the crypto data store
+ * The crypto data store.
  */
 internal interface IMXCryptoStore {
 
@@ -82,6 +81,15 @@ internal interface IMXCryptoStore {
      */
     fun setGlobalBlacklistUnverifiedDevices(block: Boolean)
 
+    /**
+     * Enable or disable key gossiping.
+     * Default is true.
+     * If set to false this device won't send key_request nor will accept key forwarded
+     */
+    fun enableKeyGossiping(enable: Boolean)
+
+    fun isKeyGossipingEnabled(): Boolean
+
     /**
      * Provides the rooms ids list in which the messages are not encrypted for the unverified devices.
      *
@@ -97,24 +105,24 @@ internal interface IMXCryptoStore {
     fun setRoomsListBlacklistUnverifiedDevices(roomIds: List)
 
     /**
-     * Get the current keys backup version
+     * Get the current keys backup version.
      */
     fun getKeyBackupVersion(): String?
 
     /**
-     * Set the current keys backup version
+     * Set the current keys backup version.
      *
      * @param keyBackupVersion the keys backup version or null to delete it
      */
     fun setKeyBackupVersion(keyBackupVersion: String?)
 
     /**
-     * Get the current keys backup local data
+     * Get the current keys backup local data.
      */
     fun getKeysBackupData(): KeysBackupDataEntity?
 
     /**
-     * Set the keys backup local data
+     * Set the keys backup local data.
      *
      * @param keysBackupData the keys backup local data, or null to erase data
      */
@@ -125,18 +133,6 @@ internal interface IMXCryptoStore {
      */
     fun getDeviceTrackingStatuses(): Map
 
-    /**
-     * @return the pending IncomingRoomKeyRequest requests
-     */
-    fun getPendingIncomingRoomKeyRequests(): List
-
-    fun getPendingIncomingGossipingRequests(): List
-
-    fun storeIncomingGossipingRequest(request: IncomingShareRequestCommon, ageLocalTS: Long?)
-
-    fun storeIncomingGossipingRequests(requests: List)
-//    fun getPendingIncomingSecretShareRequests(): List
-
     /**
      * Indicate if the store contains data for the passed account.
      *
@@ -150,12 +146,12 @@ internal interface IMXCryptoStore {
     fun deleteStore()
 
     /**
-     * open any existing crypto store
+     * open any existing crypto store.
      */
     fun open()
 
     /**
-     * Close the store
+     * Close the store.
      */
     fun close()
 
@@ -243,10 +239,10 @@ internal interface IMXCryptoStore {
     fun getRoomAlgorithm(roomId: String): String?
 
     /**
-     * This is a bit different than isRoomEncrypted
-     * A room is encrypted when there is a m.room.encryption state event in the room (malformed/invalid or not)
-     * But the crypto layer has additional guaranty to ensure that encryption would never been reverted
-     * It's defensive coding out of precaution (if ever state is reset)
+     * This is a bit different than isRoomEncrypted.
+     * A room is encrypted when there is a m.room.encryption state event in the room (malformed/invalid or not).
+     * But the crypto layer has additional guaranty to ensure that encryption would never been reverted.
+     * It's defensive coding out of precaution (if ever state is reset).
      */
     fun roomWasOnceEncrypted(roomId: String): Boolean
 
@@ -282,7 +278,7 @@ internal interface IMXCryptoStore {
     fun getDeviceSession(sessionId: String, deviceKey: String): OlmSessionWrapper?
 
     /**
-     * Retrieve the last used sessionId, regarding `lastReceivedMessageTs`, or null if no session exist
+     * Retrieve the last used sessionId, regarding `lastReceivedMessageTs`, or null if no session exist.
      *
      * @param deviceKey the public key of the other device.
      * @return last used sessionId, or null if not found
@@ -306,17 +302,17 @@ internal interface IMXCryptoStore {
     fun getInboundGroupSession(sessionId: String, senderKey: String): OlmInboundGroupSessionWrapper2?
 
     /**
-     * Get the current outbound group session for this encrypted room
+     * Get the current outbound group session for this encrypted room.
      */
     fun getCurrentOutboundGroupSessionForRoom(roomId: String): OutboundGroupSessionWrapper?
 
     /**
-     * Get the current outbound group session for this encrypted room
+     * Get the current outbound group session for this encrypted room.
      */
     fun storeCurrentOutboundGroupSessionForRoom(roomId: String, outboundGroupSession: OlmOutboundGroupSession?)
 
     /**
-     * Remove an inbound group session
+     * Remove an inbound group session.
      *
      * @param sessionId the session identifier.
      * @param senderKey the base64-encoded curve25519 key of the sender.
@@ -356,7 +352,7 @@ internal interface IMXCryptoStore {
     fun inboundGroupSessionsCount(onlyBackedUp: Boolean): Int
 
     /**
-     * Save the device statuses
+     * Save the device statuses.
      *
      * @param deviceTrackingStatuses the device tracking statuses
      */
@@ -372,12 +368,14 @@ internal interface IMXCryptoStore {
     fun getDeviceTrackingStatus(userId: String, defaultValue: Int): Int
 
     /**
-     * Look for an existing outgoing room key request, and if none is found, return null
+     * Look for an existing outgoing room key request, and if none is found, return null.
      *
      * @param requestBody the request body
      * @return an OutgoingRoomKeyRequest instance or null
      */
-    fun getOutgoingRoomKeyRequest(requestBody: RoomKeyRequestBody): OutgoingRoomKeyRequest?
+    fun getOutgoingRoomKeyRequest(requestBody: RoomKeyRequestBody): OutgoingKeyRequest?
+    fun getOutgoingRoomKeyRequest(requestId: String): OutgoingKeyRequest?
+    fun getOutgoingRoomKeyRequest(roomId: String, sessionId: String, algorithm: String, senderKey: String): List
 
     /**
      * Look for an existing outgoing room key request, and if none is found, add a new one.
@@ -385,39 +383,59 @@ internal interface IMXCryptoStore {
      * @param request the request
      * @return either the same instance as passed in, or the existing one.
      */
-    fun getOrAddOutgoingRoomKeyRequest(requestBody: RoomKeyRequestBody, recipients: Map>): OutgoingRoomKeyRequest?
+    fun getOrAddOutgoingRoomKeyRequest(requestBody: RoomKeyRequestBody, recipients: Map>, fromIndex: Int): OutgoingKeyRequest
+    fun updateOutgoingRoomKeyRequestState(requestId: String, newState: OutgoingRoomKeyRequestState)
+    fun updateOutgoingRoomKeyRequiredIndex(requestId: String, newIndex: Int)
+    fun updateOutgoingRoomKeyReply(
+            roomId: String,
+            sessionId: String,
+            algorithm: String,
+            senderKey: String,
+            fromDevice: String?,
+            event: Event)
 
-    fun getOrAddOutgoingSecretShareRequest(secretName: String, recipients: Map>): OutgoingSecretRequest?
+    fun deleteOutgoingRoomKeyRequest(requestId: String)
+    fun deleteOutgoingRoomKeyRequestInState(state: OutgoingRoomKeyRequestState)
 
-    fun saveGossipingEvent(event: Event) = saveGossipingEvents(listOf(event))
+    fun saveIncomingKeyRequestAuditTrail(
+            requestId: String,
+            roomId: String,
+            sessionId: String,
+            senderKey: String,
+            algorithm: String,
+            fromUser: String,
+            fromDevice: String
+    )
 
-    fun saveGossipingEvents(events: List)
+    fun saveWithheldAuditTrail(
+            roomId: String,
+            sessionId: String,
+            senderKey: String,
+            algorithm: String,
+            code: WithHeldCode,
+            userId: String,
+            deviceId: String
+    )
 
-    fun updateGossipingRequestState(request: IncomingShareRequestCommon, state: GossipingRequestState) {
-        updateGossipingRequestState(
-                requestUserId = request.userId,
-                requestDeviceId = request.deviceId,
-                requestId = request.requestId,
-                state = state
-        )
-    }
+    fun saveForwardKeyAuditTrail(
+            roomId: String,
+            sessionId: String,
+            senderKey: String,
+            algorithm: String,
+            userId: String,
+            deviceId: String,
+            chainIndex: Long?
+    )
 
-    fun updateGossipingRequestState(requestUserId: String?,
-                                    requestDeviceId: String?,
-                                    requestId: String?,
-                                    state: GossipingRequestState)
-
-    /**
-     * Search an IncomingRoomKeyRequest
-     *
-     * @param userId    the user id
-     * @param deviceId  the device id
-     * @param requestId the request id
-     * @return an IncomingRoomKeyRequest if it exists, else null
-     */
-    fun getIncomingRoomKeyRequest(userId: String, deviceId: String, requestId: String): IncomingRoomKeyRequest?
-
-    fun updateOutgoingGossipingRequestState(requestId: String, state: OutgoingGossipingRequestState)
+    fun saveIncomingForwardKeyAuditTrail(
+            roomId: String,
+            sessionId: String,
+            senderKey: String,
+            algorithm: String,
+            userId: String,
+            deviceId: String,
+            chainIndex: Long?
+    )
 
     fun addNewSessionListener(listener: NewSessionListener)
 
@@ -428,7 +446,7 @@ internal interface IMXCryptoStore {
     // =============================================
 
     /**
-     * Gets the current crosssigning info
+     * Gets the current crosssigning info.
      */
     fun getMyCrossSigningInfo(): MXCrossSigningInfo?
 
@@ -477,17 +495,15 @@ internal interface IMXCryptoStore {
     fun getSharedWithInfo(roomId: String?, sessionId: String): MXUsersDevicesMap
     // Dev tools
 
-    fun getOutgoingRoomKeyRequests(): List
-    fun getOutgoingRoomKeyRequestsPaged(): LiveData>
-    fun getOutgoingSecretKeyRequests(): List
-    fun getOutgoingSecretRequest(secretName: String): OutgoingSecretRequest?
-    fun getIncomingRoomKeyRequests(): List
-    fun getIncomingRoomKeyRequestsPaged(): LiveData>
-    fun getGossipingEventsTrail(): LiveData>
-    fun getGossipingEvents(): List
+    fun getOutgoingRoomKeyRequests(): List
+    fun getOutgoingRoomKeyRequestsPaged(): LiveData>
+    fun getGossipingEventsTrail(): LiveData>
+    fun  getGossipingEventsTrail(type: TrailType, mapper: ((AuditTrail) -> T)): LiveData>
+    fun getGossipingEvents(): List
 
     fun setDeviceKeysUploaded(uploaded: Boolean)
     fun areDeviceKeysUploaded(): Boolean
     fun tidyUpDataBase()
     fun logDbUsageInfo()
+    fun getOutgoingRoomKeyRequests(inStates: Set): List
 }
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/Helper.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/Helper.kt
index b841e9c6e5..2d66ce1488 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/Helper.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/Helper.kt
@@ -26,7 +26,7 @@ import java.util.zip.GZIPInputStream
 import java.util.zip.GZIPOutputStream
 
 /**
- * Get realm, invoke the action, close realm, and return the result of the action
+ * Get realm, invoke the action, close realm, and return the result of the action.
  */
 internal fun  doWithRealm(realmConfiguration: RealmConfiguration, action: (Realm) -> T): T {
     return Realm.getInstance(realmConfiguration).use { realm ->
@@ -35,7 +35,7 @@ internal fun  doWithRealm(realmConfiguration: RealmConfiguration, action: (Re
 }
 
 /**
- * Get realm, do the query, copy from realm, close realm, and return the copied result
+ * Get realm, do the query, copy from realm, close realm, and return the copied result.
  */
 internal fun  doRealmQueryAndCopy(realmConfiguration: RealmConfiguration, action: (Realm) -> T?): T? {
     return Realm.getInstance(realmConfiguration).use { realm ->
@@ -44,7 +44,7 @@ internal fun  doRealmQueryAndCopy(realmConfiguration: RealmConf
 }
 
 /**
- * Get realm, do the list query, copy from realm, close realm, and return the copied result
+ * Get realm, do the list query, copy from realm, close realm, and return the copied result.
  */
 internal fun  doRealmQueryAndCopyList(realmConfiguration: RealmConfiguration, action: (Realm) -> Iterable): Iterable {
     return Realm.getInstance(realmConfiguration).use { realm ->
@@ -53,7 +53,7 @@ internal fun  doRealmQueryAndCopyList(realmConfiguration: Realm
 }
 
 /**
- * Get realm instance, invoke the action in a transaction and close realm
+ * Get realm instance, invoke the action in a transaction and close realm.
  */
 internal fun doRealmTransaction(realmConfiguration: RealmConfiguration, action: (Realm) -> Unit) {
     Realm.getInstance(realmConfiguration).use { realm ->
@@ -68,7 +68,7 @@ internal fun doRealmTransactionAsync(realmConfiguration: RealmConfiguration, act
 }
 
 /**
- * Serialize any Serializable object, zip it and convert to Base64 String
+ * Serialize any Serializable object, zip it and convert to Base64 String.
  */
 internal fun serializeForRealm(o: Any?): String? {
     if (o == null) {
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/RealmCryptoStore.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/RealmCryptoStore.kt
index 99adbbfbae..533ab70bba 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/RealmCryptoStore.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/RealmCryptoStore.kt
@@ -24,37 +24,40 @@ import com.zhuinden.monarchy.Monarchy
 import io.realm.Realm
 import io.realm.RealmConfiguration
 import io.realm.Sort
+import io.realm.kotlin.createObject
 import io.realm.kotlin.where
 import org.matrix.android.sdk.api.crypto.MXCRYPTO_ALGORITHM_MEGOLM
 import org.matrix.android.sdk.api.extensions.tryOrNull
+import org.matrix.android.sdk.api.logger.LoggerTag
 import org.matrix.android.sdk.api.session.crypto.NewSessionListener
+import org.matrix.android.sdk.api.session.crypto.OutgoingKeyRequest
+import org.matrix.android.sdk.api.session.crypto.OutgoingRoomKeyRequestState
 import org.matrix.android.sdk.api.session.crypto.crosssigning.CryptoCrossSigningKey
 import org.matrix.android.sdk.api.session.crypto.crosssigning.MXCrossSigningInfo
 import org.matrix.android.sdk.api.session.crypto.crosssigning.PrivateKeysInfo
 import org.matrix.android.sdk.api.session.crypto.keysbackup.SavedKeyBackupKeyInfo
+import org.matrix.android.sdk.api.session.crypto.model.AuditTrail
 import org.matrix.android.sdk.api.session.crypto.model.CryptoDeviceInfo
 import org.matrix.android.sdk.api.session.crypto.model.DeviceInfo
-import org.matrix.android.sdk.api.session.crypto.model.GossipingRequestState
-import org.matrix.android.sdk.api.session.crypto.model.IncomingRoomKeyRequest
-import org.matrix.android.sdk.api.session.crypto.model.IncomingSecretShareRequest
+import org.matrix.android.sdk.api.session.crypto.model.ForwardInfo
+import org.matrix.android.sdk.api.session.crypto.model.IncomingKeyRequestInfo
 import org.matrix.android.sdk.api.session.crypto.model.MXUsersDevicesMap
-import org.matrix.android.sdk.api.session.crypto.model.OlmDecryptionResult
-import org.matrix.android.sdk.api.session.crypto.model.OutgoingGossipingRequestState
-import org.matrix.android.sdk.api.session.crypto.model.OutgoingRoomKeyRequest
 import org.matrix.android.sdk.api.session.crypto.model.RoomKeyRequestBody
+import org.matrix.android.sdk.api.session.crypto.model.TrailType
+import org.matrix.android.sdk.api.session.crypto.model.WithheldInfo
 import org.matrix.android.sdk.api.session.events.model.Event
 import org.matrix.android.sdk.api.session.events.model.content.RoomKeyWithHeldContent
-import org.matrix.android.sdk.api.session.room.send.SendState
+import org.matrix.android.sdk.api.session.events.model.content.WithHeldCode
 import org.matrix.android.sdk.api.util.Optional
 import org.matrix.android.sdk.api.util.toOptional
-import org.matrix.android.sdk.internal.crypto.GossipRequestType
-import org.matrix.android.sdk.internal.crypto.IncomingShareRequestCommon
-import org.matrix.android.sdk.internal.crypto.OutgoingSecretRequest
 import org.matrix.android.sdk.internal.crypto.model.OlmInboundGroupSessionWrapper2
 import org.matrix.android.sdk.internal.crypto.model.OlmSessionWrapper
 import org.matrix.android.sdk.internal.crypto.model.OutboundGroupSessionWrapper
 import org.matrix.android.sdk.internal.crypto.store.IMXCryptoStore
 import org.matrix.android.sdk.internal.crypto.store.db.mapper.CrossSigningKeysMapper
+import org.matrix.android.sdk.internal.crypto.store.db.model.AuditTrailEntity
+import org.matrix.android.sdk.internal.crypto.store.db.model.AuditTrailEntityFields
+import org.matrix.android.sdk.internal.crypto.store.db.model.AuditTrailMapper
 import org.matrix.android.sdk.internal.crypto.store.db.model.CrossSigningInfoEntity
 import org.matrix.android.sdk.internal.crypto.store.db.model.CrossSigningInfoEntityFields
 import org.matrix.android.sdk.internal.crypto.store.db.model.CryptoMapper
@@ -63,10 +66,6 @@ import org.matrix.android.sdk.internal.crypto.store.db.model.CryptoRoomEntity
 import org.matrix.android.sdk.internal.crypto.store.db.model.CryptoRoomEntityFields
 import org.matrix.android.sdk.internal.crypto.store.db.model.DeviceInfoEntity
 import org.matrix.android.sdk.internal.crypto.store.db.model.DeviceInfoEntityFields
-import org.matrix.android.sdk.internal.crypto.store.db.model.GossipingEventEntity
-import org.matrix.android.sdk.internal.crypto.store.db.model.GossipingEventEntityFields
-import org.matrix.android.sdk.internal.crypto.store.db.model.IncomingGossipingRequestEntity
-import org.matrix.android.sdk.internal.crypto.store.db.model.IncomingGossipingRequestEntityFields
 import org.matrix.android.sdk.internal.crypto.store.db.model.KeysBackupDataEntity
 import org.matrix.android.sdk.internal.crypto.store.db.model.MyDeviceLastSeenInfoEntity
 import org.matrix.android.sdk.internal.crypto.store.db.model.OlmInboundGroupSessionEntity
@@ -74,8 +73,8 @@ import org.matrix.android.sdk.internal.crypto.store.db.model.OlmInboundGroupSess
 import org.matrix.android.sdk.internal.crypto.store.db.model.OlmSessionEntity
 import org.matrix.android.sdk.internal.crypto.store.db.model.OlmSessionEntityFields
 import org.matrix.android.sdk.internal.crypto.store.db.model.OutboundGroupSessionInfoEntity
-import org.matrix.android.sdk.internal.crypto.store.db.model.OutgoingGossipingRequestEntity
-import org.matrix.android.sdk.internal.crypto.store.db.model.OutgoingGossipingRequestEntityFields
+import org.matrix.android.sdk.internal.crypto.store.db.model.OutgoingKeyRequestEntity
+import org.matrix.android.sdk.internal.crypto.store.db.model.OutgoingKeyRequestEntityFields
 import org.matrix.android.sdk.internal.crypto.store.db.model.SharedSessionEntity
 import org.matrix.android.sdk.internal.crypto.store.db.model.TrustLevelEntity
 import org.matrix.android.sdk.internal.crypto.store.db.model.UserEntity
@@ -89,7 +88,6 @@ import org.matrix.android.sdk.internal.crypto.store.db.query.get
 import org.matrix.android.sdk.internal.crypto.store.db.query.getById
 import org.matrix.android.sdk.internal.crypto.store.db.query.getOrCreate
 import org.matrix.android.sdk.internal.crypto.util.RequestIdHelper
-import org.matrix.android.sdk.internal.database.mapper.ContentMapper
 import org.matrix.android.sdk.internal.database.tools.RealmDebugTools
 import org.matrix.android.sdk.internal.di.CryptoDatabase
 import org.matrix.android.sdk.internal.di.DeviceId
@@ -97,6 +95,7 @@ import org.matrix.android.sdk.internal.di.MoshiProvider
 import org.matrix.android.sdk.internal.di.UserId
 import org.matrix.android.sdk.internal.extensions.clearWith
 import org.matrix.android.sdk.internal.session.SessionScope
+import org.matrix.android.sdk.internal.util.time.Clock
 import org.matrix.olm.OlmAccount
 import org.matrix.olm.OlmException
 import org.matrix.olm.OlmOutboundGroupSession
@@ -105,12 +104,15 @@ import java.util.concurrent.Executors
 import java.util.concurrent.TimeUnit
 import javax.inject.Inject
 
+private val loggerTag = LoggerTag("RealmCryptoStore", LoggerTag.CRYPTO)
+
 @SessionScope
 internal class RealmCryptoStore @Inject constructor(
         @CryptoDatabase private val realmConfiguration: RealmConfiguration,
         private val crossSigningKeysMapper: CrossSigningKeysMapper,
         @UserId private val userId: String,
-        @DeviceId private val deviceId: String?
+        @DeviceId private val deviceId: String?,
+        private val clock: Clock,
 ) : IMXCryptoStore {
 
     /* ==========================================================================================
@@ -231,7 +233,7 @@ internal class RealmCryptoStore @Inject constructor(
     }
 
     /**
-     * Olm account access should be synchronized
+     * Olm account access should be synchronized.
      */
     override fun  doWithOlmAccount(block: (OlmAccount) -> T): T {
         return olmAccount!!.let { olmAccount ->
@@ -271,12 +273,13 @@ internal class RealmCryptoStore @Inject constructor(
     }
 
     override fun deviceWithIdentityKey(identityKey: String): CryptoDeviceInfo? {
-        return doWithRealm(realmConfiguration) {
-            it.where()
-                    .equalTo(DeviceInfoEntityFields.IDENTITY_KEY, identityKey)
-                    .findFirst()
-                    ?.let { deviceInfo ->
-                        CryptoMapper.mapToModel(deviceInfo)
+        return doWithRealm(realmConfiguration) { realm ->
+            realm.where()
+                    .contains(DeviceInfoEntityFields.KEYS_MAP_JSON, identityKey)
+                    .findAll()
+                    .mapNotNull { CryptoMapper.mapToModel(it) }
+                    .firstOrNull {
+                        it.identityKey() == identityKey
                     }
         }
     }
@@ -307,7 +310,7 @@ internal class RealmCryptoStore @Inject constructor(
                         // Add the device
                         Timber.d("Add device ${cryptoDeviceInfo.deviceId} of user $userId")
                         val newEntity = CryptoMapper.mapToEntity(cryptoDeviceInfo)
-                        newEntity.firstTimeSeenLocalTs = System.currentTimeMillis()
+                        newEntity.firstTimeSeenLocalTs = clock.epochMillis()
                         userEntity.devices.add(newEntity)
                     } else {
                         // Update the device
@@ -715,6 +718,7 @@ internal class RealmCryptoStore @Inject constructor(
         return doWithRealm(realmConfiguration) {
             it.where()
                     .equalTo(OlmSessionEntityFields.DEVICE_KEY, deviceKey)
+                    .sort(OlmSessionEntityFields.LAST_RECEIVED_MESSAGE_TS, Sort.DESCENDING)
                     .findAll()
                     .mapNotNull { sessionEntity ->
                         sessionEntity.sessionId
@@ -740,14 +744,23 @@ internal class RealmCryptoStore @Inject constructor(
                 if (sessionIdentifier != null) {
                     val key = OlmInboundGroupSessionEntity.createPrimaryKey(sessionIdentifier, session.senderKey)
 
-                    val realmOlmInboundGroupSession = OlmInboundGroupSessionEntity().apply {
-                        primaryKey = key
-                        sessionId = sessionIdentifier
-                        senderKey = session.senderKey
-                        putInboundGroupSession(session)
-                    }
+                    val existing = realm.where()
+                            .equalTo(OlmInboundGroupSessionEntityFields.PRIMARY_KEY, key)
+                            .findFirst()
 
-                    realm.insertOrUpdate(realmOlmInboundGroupSession)
+                    if (existing != null) {
+                        // we want to keep the existing backup status
+                        existing.putInboundGroupSession(session)
+                    } else {
+                        val realmOlmInboundGroupSession = OlmInboundGroupSessionEntity().apply {
+                            primaryKey = key
+                            sessionId = sessionIdentifier
+                            senderKey = session.senderKey
+                            putInboundGroupSession(session)
+                        }
+
+                        realm.insertOrUpdate(realmOlmInboundGroupSession)
+                    }
                 }
             }
         }
@@ -791,7 +804,7 @@ internal class RealmCryptoStore @Inject constructor(
 
                 if (outboundGroupSession != null) {
                     val info = realm.createObject(OutboundGroupSessionInfoEntity::class.java).apply {
-                        creationTime = System.currentTimeMillis()
+                        creationTime = clock.epochMillis()
                         putOutboundGroupSession(outboundGroupSession)
                     }
                     entity.outboundSessionInfo = info
@@ -802,7 +815,7 @@ internal class RealmCryptoStore @Inject constructor(
 
     /**
      * Note: the result will be only use to export all the keys and not to use the OlmInboundGroupSessionWrapper2,
-     * so there is no need to use or update `inboundGroupSessionToRelease` for native memory management
+     * so there is no need to use or update `inboundGroupSessionToRelease` for native memory management.
      */
     override fun getInboundGroupSessions(): List {
         return doWithRealm(realmConfiguration) {
@@ -876,17 +889,33 @@ internal class RealmCryptoStore @Inject constructor(
             return
         }
 
-        doRealmTransaction(realmConfiguration) {
+        doRealmTransaction(realmConfiguration) { realm ->
             olmInboundGroupSessionWrappers.forEach { olmInboundGroupSessionWrapper ->
                 try {
+                    val sessionIdentifier = olmInboundGroupSessionWrapper.olmInboundGroupSession?.sessionIdentifier()
                     val key = OlmInboundGroupSessionEntity.createPrimaryKey(
-                            olmInboundGroupSessionWrapper.olmInboundGroupSession?.sessionIdentifier(),
-                            olmInboundGroupSessionWrapper.senderKey)
+                            sessionIdentifier,
+                            olmInboundGroupSessionWrapper.senderKey
+                    )
 
-                    it.where()
+                    val existing = realm.where()
                             .equalTo(OlmInboundGroupSessionEntityFields.PRIMARY_KEY, key)
                             .findFirst()
-                            ?.backedUp = true
+
+                    if (existing != null) {
+                        existing.backedUp = true
+                    } else {
+                        // ... might be in cache but not yet persisted, create a record to persist backedup state
+                        val realmOlmInboundGroupSession = OlmInboundGroupSessionEntity().apply {
+                            primaryKey = key
+                            sessionId = sessionIdentifier
+                            senderKey = olmInboundGroupSessionWrapper.senderKey
+                            putInboundGroupSession(olmInboundGroupSessionWrapper)
+                            backedUp = true
+                        }
+
+                        realm.insertOrUpdate(realmOlmInboundGroupSession)
+                    }
                 } catch (e: OlmException) {
                     Timber.e(e, "OlmException")
                 }
@@ -925,6 +954,18 @@ internal class RealmCryptoStore @Inject constructor(
         }
     }
 
+    override fun enableKeyGossiping(enable: Boolean) {
+        doRealmTransaction(realmConfiguration) {
+            it.where().findFirst()?.globalEnableKeyGossiping = enable
+        }
+    }
+
+    override fun isKeyGossipingEnabled(): Boolean {
+        return doWithRealm(realmConfiguration) {
+            it.where().findFirst()?.globalEnableKeyGossiping
+        } ?: true
+    }
+
     override fun getGlobalBlacklistUnverifiedDevices(): Boolean {
         return doWithRealm(realmConfiguration) {
             it.where().findFirst()?.globalBlacklistUnverifiedDevices
@@ -1006,12 +1047,13 @@ internal class RealmCryptoStore @Inject constructor(
                 ?: defaultValue
     }
 
-    override fun getOutgoingRoomKeyRequest(requestBody: RoomKeyRequestBody): OutgoingRoomKeyRequest? {
+    override fun getOutgoingRoomKeyRequest(requestBody: RoomKeyRequestBody): OutgoingKeyRequest? {
         return monarchy.fetchAllCopiedSync { realm ->
-            realm.where()
-                    .equalTo(OutgoingGossipingRequestEntityFields.TYPE_STR, GossipRequestType.KEY.name)
-        }.mapNotNull {
-            it.toOutgoingGossipingRequest() as? OutgoingRoomKeyRequest
+            realm.where()
+                    .equalTo(OutgoingKeyRequestEntityFields.ROOM_ID, requestBody.roomId)
+                    .equalTo(OutgoingKeyRequestEntityFields.MEGOLM_SESSION_ID, requestBody.sessionId)
+        }.map {
+            it.toOutgoingKeyRequest()
         }.firstOrNull {
             it.requestBody?.algorithm == requestBody.algorithm &&
                     it.requestBody?.roomId == requestBody.roomId &&
@@ -1020,87 +1062,118 @@ internal class RealmCryptoStore @Inject constructor(
         }
     }
 
-    override fun getOutgoingSecretRequest(secretName: String): OutgoingSecretRequest? {
+    override fun getOutgoingRoomKeyRequest(requestId: String): OutgoingKeyRequest? {
         return monarchy.fetchAllCopiedSync { realm ->
-            realm.where()
-                    .equalTo(OutgoingGossipingRequestEntityFields.TYPE_STR, GossipRequestType.SECRET.name)
-                    .equalTo(OutgoingGossipingRequestEntityFields.REQUESTED_INFO_STR, secretName)
-        }.mapNotNull {
-            it.toOutgoingGossipingRequest() as? OutgoingSecretRequest
+            realm.where()
+                    .equalTo(OutgoingKeyRequestEntityFields.REQUEST_ID, requestId)
+        }.map {
+            it.toOutgoingKeyRequest()
         }.firstOrNull()
     }
 
-    override fun getIncomingRoomKeyRequests(): List {
+    override fun getOutgoingRoomKeyRequest(roomId: String, sessionId: String, algorithm: String, senderKey: String): List {
+        // TODO this annoying we have to load all
         return monarchy.fetchAllCopiedSync { realm ->
-            realm.where()
-                    .equalTo(IncomingGossipingRequestEntityFields.TYPE_STR, GossipRequestType.KEY.name)
-        }.mapNotNull {
-            it.toIncomingGossipingRequest() as? IncomingRoomKeyRequest
+            realm.where()
+                    .equalTo(OutgoingKeyRequestEntityFields.ROOM_ID, roomId)
+                    .equalTo(OutgoingKeyRequestEntityFields.MEGOLM_SESSION_ID, sessionId)
+        }.map {
+            it.toOutgoingKeyRequest()
+        }.filter {
+            it.requestBody?.algorithm == algorithm &&
+                    it.requestBody?.senderKey == senderKey
         }
     }
 
-    override fun getIncomingRoomKeyRequestsPaged(): LiveData> {
+    override fun getGossipingEventsTrail(): LiveData> {
         val realmDataSourceFactory = monarchy.createDataSourceFactory { realm ->
-            realm.where()
-                    .equalTo(IncomingGossipingRequestEntityFields.TYPE_STR, GossipRequestType.KEY.name)
-                    .sort(IncomingGossipingRequestEntityFields.LOCAL_CREATION_TIMESTAMP, Sort.DESCENDING)
+            realm.where().sort(AuditTrailEntityFields.AGE_LOCAL_TS, Sort.DESCENDING)
         }
         val dataSourceFactory = realmDataSourceFactory.map {
-            it.toIncomingGossipingRequest() as? IncomingRoomKeyRequest
-                    ?: IncomingRoomKeyRequest(
-                            requestBody = null,
-                            deviceId = "",
-                            userId = "",
-                            requestId = "",
-                            state = GossipingRequestState.NONE,
-                            localCreationTimestamp = 0
-                    )
+            AuditTrailMapper.map(it)
+            // mm we can't map not null...
+                    ?: createUnknownTrail()
         }
-        return monarchy.findAllPagedWithChanges(realmDataSourceFactory,
-                LivePagedListBuilder(dataSourceFactory,
+        return monarchy.findAllPagedWithChanges(
+                realmDataSourceFactory,
+                LivePagedListBuilder(
+                        dataSourceFactory,
                         PagedList.Config.Builder()
                                 .setPageSize(20)
                                 .setEnablePlaceholders(false)
                                 .setPrefetchDistance(1)
-                                .build())
+                                .build()
+                )
         )
     }
 
-    override fun getGossipingEventsTrail(): LiveData> {
+    private fun createUnknownTrail() = AuditTrail(
+            clock.epochMillis(),
+            TrailType.Unknown,
+            IncomingKeyRequestInfo(
+                    "",
+                    "",
+                    "",
+                    "",
+                    "",
+                    "",
+                    "",
+            )
+    )
+
+    override fun  getGossipingEventsTrail(type: TrailType, mapper: ((AuditTrail) -> T)): LiveData> {
         val realmDataSourceFactory = monarchy.createDataSourceFactory { realm ->
-            realm.where().sort(GossipingEventEntityFields.AGE_LOCAL_TS, Sort.DESCENDING)
+            realm.where()
+                    .equalTo(AuditTrailEntityFields.TYPE, type.name)
+                    .sort(AuditTrailEntityFields.AGE_LOCAL_TS, Sort.DESCENDING)
         }
-        val dataSourceFactory = realmDataSourceFactory.map { it.toModel() }
-        val trail = monarchy.findAllPagedWithChanges(realmDataSourceFactory,
-                LivePagedListBuilder(dataSourceFactory,
+        val dataSourceFactory = realmDataSourceFactory.map { entity ->
+            (AuditTrailMapper.map(entity)
+            // mm we can't map not null...
+                    ?: createUnknownTrail()
+                    ).let { mapper.invoke(it) }
+        }
+        return monarchy.findAllPagedWithChanges(
+                realmDataSourceFactory,
+                LivePagedListBuilder(
+                        dataSourceFactory,
                         PagedList.Config.Builder()
                                 .setPageSize(20)
                                 .setEnablePlaceholders(false)
                                 .setPrefetchDistance(1)
-                                .build())
+                                .build()
+                )
         )
-        return trail
     }
 
-    override fun getGossipingEvents(): List {
+    override fun getGossipingEvents(): List {
         return monarchy.fetchAllCopiedSync { realm ->
-            realm.where()
-        }.map {
-            it.toModel()
+            realm.where()
+        }.mapNotNull {
+            AuditTrailMapper.map(it)
         }
     }
 
-    override fun getOrAddOutgoingRoomKeyRequest(requestBody: RoomKeyRequestBody, recipients: Map>): OutgoingRoomKeyRequest? {
+    override fun getOrAddOutgoingRoomKeyRequest(requestBody: RoomKeyRequestBody,
+                                                recipients: Map>,
+                                                fromIndex: Int): OutgoingKeyRequest {
         // Insert the request and return the one passed in parameter
-        var request: OutgoingRoomKeyRequest? = null
+        lateinit var request: OutgoingKeyRequest
         doRealmTransaction(realmConfiguration) { realm ->
 
-            val existing = realm.where()
-                    .equalTo(OutgoingGossipingRequestEntityFields.TYPE_STR, GossipRequestType.KEY.name)
+            val existing = realm.where()
+                    .equalTo(OutgoingKeyRequestEntityFields.MEGOLM_SESSION_ID, requestBody.sessionId)
+                    .equalTo(OutgoingKeyRequestEntityFields.ROOM_ID, requestBody.roomId)
                     .findAll()
-                    .mapNotNull {
-                        it.toOutgoingGossipingRequest() as? OutgoingRoomKeyRequest
-                    }.firstOrNull {
+                    .map {
+                        it.toOutgoingKeyRequest()
+                    }.also {
+                        if (it.size > 1) {
+                            // there should be one or zero but not more, worth warning
+                            Timber.tag(loggerTag.value).w("There should not be more than one active key request per session")
+                        }
+                    }
+                    .firstOrNull {
                         it.requestBody?.algorithm == requestBody.algorithm &&
                                 it.requestBody?.sessionId == requestBody.sessionId &&
                                 it.requestBody?.senderKey == requestBody.senderKey &&
@@ -1108,13 +1181,14 @@ internal class RealmCryptoStore @Inject constructor(
                     }
 
             if (existing == null) {
-                request = realm.createObject(OutgoingGossipingRequestEntity::class.java).apply {
+                request = realm.createObject(OutgoingKeyRequestEntity::class.java).apply {
                     this.requestId = RequestIdHelper.createUniqueRequestId()
                     this.setRecipients(recipients)
-                    this.requestState = OutgoingGossipingRequestState.UNSENT
-                    this.type = GossipRequestType.KEY
-                    this.requestedInfoStr = requestBody.toJson()
-                }.toOutgoingGossipingRequest() as? OutgoingRoomKeyRequest
+                    this.requestedIndex = fromIndex
+                    this.requestState = OutgoingRoomKeyRequestState.UNSENT
+                    this.setRequestBody(requestBody)
+                    this.creationTimeStamp = clock.epochMillis()
+                }.toOutgoingKeyRequest()
             } else {
                 request = existing
             }
@@ -1122,284 +1196,175 @@ internal class RealmCryptoStore @Inject constructor(
         return request
     }
 
-    override fun getOrAddOutgoingSecretShareRequest(secretName: String, recipients: Map>): OutgoingSecretRequest? {
-        var request: OutgoingSecretRequest? = null
-
-        // Insert the request and return the one passed in parameter
+    override fun updateOutgoingRoomKeyRequestState(requestId: String, newState: OutgoingRoomKeyRequestState) {
         doRealmTransaction(realmConfiguration) { realm ->
-            val existing = realm.where()
-                    .equalTo(OutgoingGossipingRequestEntityFields.TYPE_STR, GossipRequestType.SECRET.name)
-                    .equalTo(OutgoingGossipingRequestEntityFields.REQUESTED_INFO_STR, secretName)
-                    .findAll()
-                    .mapNotNull {
-                        it.toOutgoingGossipingRequest() as? OutgoingSecretRequest
-                    }.firstOrNull()
-            if (existing == null) {
-                request = realm.createObject(OutgoingGossipingRequestEntity::class.java).apply {
-                    this.type = GossipRequestType.SECRET
-                    setRecipients(recipients)
-                    this.requestState = OutgoingGossipingRequestState.UNSENT
-                    this.requestId = RequestIdHelper.createUniqueRequestId()
-                    this.requestedInfoStr = secretName
-                }.toOutgoingGossipingRequest() as? OutgoingSecretRequest
-            } else {
-                request = existing
-            }
-        }
-
-        return request
-    }
-
-    override fun saveGossipingEvents(events: List) {
-        monarchy.writeAsync { realm ->
-            val now = System.currentTimeMillis()
-            events.forEach { event ->
-                val ageLocalTs = event.unsignedData?.age?.let { now - it } ?: now
-                val entity = GossipingEventEntity(
-                        type = event.type,
-                        sender = event.senderId,
-                        ageLocalTs = ageLocalTs,
-                        content = ContentMapper.map(event.content)
-                ).apply {
-                    sendState = SendState.SYNCED
-                    decryptionResultJson = MoshiProvider.providesMoshi().adapter(OlmDecryptionResult::class.java).toJson(event.mxDecryptionResult)
-                    decryptionErrorCode = event.mCryptoError?.name
-                }
-                realm.insertOrUpdate(entity)
-            }
-        }
-    }
-
-//    override fun getOutgoingRoomKeyRequestByState(states: Set): OutgoingRoomKeyRequest? {
-//        val statesIndex = states.map { it.ordinal }.toTypedArray()
-//        return doRealmQueryAndCopy(realmConfiguration) { realm ->
-//            realm.where()
-//                    .equalTo(GossipingEventEntityFields.SENDER, credentials.userId)
-//                    .findAll()
-//                    .filter {entity ->
-//                        states.any { it == entity.requestState}
-//                    }
-//        }.mapNotNull {
-//            ContentMapper.map(it.content)?.toModel()
-//        }
-//                ?.toOutgoingRoomKeyRequest()
-//    }
-//
-//    override fun getOutgoingSecretShareRequestByState(states: Set): OutgoingSecretRequest? {
-//        val statesIndex = states.map { it.ordinal }.toTypedArray()
-//        return doRealmQueryAndCopy(realmConfiguration) {
-//            it.where()
-//                    .`in`(OutgoingSecretRequestEntityFields.STATE, statesIndex)
-//                    .findFirst()
-//        }
-//                ?.toOutgoingSecretRequest()
-//    }
-
-//    override fun updateOutgoingRoomKeyRequest(request: OutgoingRoomKeyRequest) {
-//        doRealmTransaction(realmConfiguration) {
-//            val obj = OutgoingRoomKeyRequestEntity().apply {
-//                requestId = request.requestId
-//                cancellationTxnId = request.cancellationTxnId
-//                state = request.state.ordinal
-//                putRecipients(request.recipients)
-//                putRequestBody(request.requestBody)
-//            }
-//
-//            it.insertOrUpdate(obj)
-//        }
-//    }
-
-//    override fun deleteOutgoingRoomKeyRequest(transactionId: String) {
-//        doRealmTransaction(realmConfiguration) {
-//            it.where()
-//                    .equalTo(OutgoingRoomKeyRequestEntityFields.REQUEST_ID, transactionId)
-//                    .findFirst()
-//                    ?.deleteFromRealm()
-//        }
-//    }
-
-//    override fun storeIncomingRoomKeyRequest(incomingRoomKeyRequest: IncomingRoomKeyRequest?) {
-//        if (incomingRoomKeyRequest == null) {
-//            return
-//        }
-//
-//        doRealmTransaction(realmConfiguration) {
-//            // Delete any previous store request with the same parameters
-//            it.where()
-//                    .equalTo(IncomingRoomKeyRequestEntityFields.USER_ID, incomingRoomKeyRequest.userId)
-//                    .equalTo(IncomingRoomKeyRequestEntityFields.DEVICE_ID, incomingRoomKeyRequest.deviceId)
-//                    .equalTo(IncomingRoomKeyRequestEntityFields.REQUEST_ID, incomingRoomKeyRequest.requestId)
-//                    .findAll()
-//                    .deleteAllFromRealm()
-//
-//            // Then store it
-//            it.createObject(IncomingRoomKeyRequestEntity::class.java).apply {
-//                userId = incomingRoomKeyRequest.userId
-//                deviceId = incomingRoomKeyRequest.deviceId
-//                requestId = incomingRoomKeyRequest.requestId
-//                putRequestBody(incomingRoomKeyRequest.requestBody)
-//            }
-//        }
-//    }
-
-//    override fun deleteIncomingRoomKeyRequest(incomingRoomKeyRequest: IncomingShareRequestCommon) {
-//        doRealmTransaction(realmConfiguration) {
-//            it.where()
-//                    .equalTo(GossipingEventEntityFields.TYPE, EventType.ROOM_KEY_REQUEST)
-//                    .notEqualTo(GossipingEventEntityFields.SENDER, credentials.userId)
-//                    .findAll()
-//                    .filter {
-//                        ContentMapper.map(it.content).toModel()?.let {
-//
-//                        }
-//                    }
-// //                    .equalTo(IncomingRoomKeyRequestEntityFields.USER_ID, incomingRoomKeyRequest.userId)
-// //                    .equalTo(IncomingRoomKeyRequestEntityFields.DEVICE_ID, incomingRoomKeyRequest.deviceId)
-// //                    .equalTo(IncomingRoomKeyRequestEntityFields.REQUEST_ID, incomingRoomKeyRequest.requestId)
-// //                    .findAll()
-// //                    .deleteAllFromRealm()
-//        }
-//    }
-
-    override fun updateGossipingRequestState(requestUserId: String?,
-                                             requestDeviceId: String?,
-                                             requestId: String?,
-                                             state: GossipingRequestState) {
-        doRealmTransaction(realmConfiguration) { realm ->
-            realm.where()
-                    .equalTo(IncomingGossipingRequestEntityFields.OTHER_USER_ID, requestUserId)
-                    .equalTo(IncomingGossipingRequestEntityFields.OTHER_DEVICE_ID, requestDeviceId)
-                    .equalTo(IncomingGossipingRequestEntityFields.REQUEST_ID, requestId)
-                    .findAll().forEach {
-                        it.requestState = state
-                    }
-        }
-    }
-
-    override fun updateOutgoingGossipingRequestState(requestId: String, state: OutgoingGossipingRequestState) {
-        doRealmTransaction(realmConfiguration) { realm ->
-            realm.where()
-                    .equalTo(OutgoingGossipingRequestEntityFields.REQUEST_ID, requestId)
-                    .findAll().forEach {
-                        it.requestState = state
-                    }
-        }
-    }
-
-    override fun getIncomingRoomKeyRequest(userId: String, deviceId: String, requestId: String): IncomingRoomKeyRequest? {
-        return doWithRealm(realmConfiguration) { realm ->
-            realm.where()
-                    .equalTo(IncomingGossipingRequestEntityFields.TYPE_STR, GossipRequestType.KEY.name)
-                    .equalTo(IncomingGossipingRequestEntityFields.OTHER_DEVICE_ID, deviceId)
-                    .equalTo(IncomingGossipingRequestEntityFields.OTHER_USER_ID, userId)
-                    .findAll()
-                    .mapNotNull { entity ->
-                        entity.toIncomingGossipingRequest() as? IncomingRoomKeyRequest
-                    }
-                    .firstOrNull()
-        }
-    }
-
-    override fun getPendingIncomingRoomKeyRequests(): List {
-        return doWithRealm(realmConfiguration) {
-            it.where()
-                    .equalTo(IncomingGossipingRequestEntityFields.TYPE_STR, GossipRequestType.KEY.name)
-                    .equalTo(IncomingGossipingRequestEntityFields.REQUEST_STATE_STR, GossipingRequestState.PENDING.name)
-                    .findAll()
-                    .map { entity ->
-                        IncomingRoomKeyRequest(
-                                userId = entity.otherUserId,
-                                deviceId = entity.otherDeviceId,
-                                requestId = entity.requestId,
-                                requestBody = entity.getRequestedKeyInfo(),
-                                localCreationTimestamp = entity.localCreationTimestamp
-                        )
-                    }
-        }
-    }
-
-    override fun getPendingIncomingGossipingRequests(): List {
-        return doWithRealm(realmConfiguration) {
-            it.where()
-                    .equalTo(IncomingGossipingRequestEntityFields.REQUEST_STATE_STR, GossipingRequestState.PENDING.name)
-                    .findAll()
-                    .mapNotNull { entity ->
-                        when (entity.type) {
-                            GossipRequestType.KEY    -> {
-                                IncomingRoomKeyRequest(
-                                        userId = entity.otherUserId,
-                                        deviceId = entity.otherDeviceId,
-                                        requestId = entity.requestId,
-                                        requestBody = entity.getRequestedKeyInfo(),
-                                        localCreationTimestamp = entity.localCreationTimestamp
-                                )
-                            }
-                            GossipRequestType.SECRET -> {
-                                IncomingSecretShareRequest(
-                                        userId = entity.otherUserId,
-                                        deviceId = entity.otherDeviceId,
-                                        requestId = entity.requestId,
-                                        secretName = entity.getRequestedSecretName(),
-                                        localCreationTimestamp = entity.localCreationTimestamp
-                                )
-                            }
+            realm.where()
+                    .equalTo(OutgoingKeyRequestEntityFields.REQUEST_ID, requestId)
+                    .findFirst()?.apply {
+                        this.requestState = newState
+                        if (newState == OutgoingRoomKeyRequestState.UNSENT) {
+                            // clear the old replies
+                            this.replies.deleteAllFromRealm()
                         }
                     }
         }
     }
 
-    override fun storeIncomingGossipingRequest(request: IncomingShareRequestCommon, ageLocalTS: Long?) {
-        doRealmTransactionAsync(realmConfiguration) { realm ->
-
-            // After a clear cache, we might have a
-
-            realm.createObject(IncomingGossipingRequestEntity::class.java).let {
-                it.otherDeviceId = request.deviceId
-                it.otherUserId = request.userId
-                it.requestId = request.requestId ?: ""
-                it.requestState = GossipingRequestState.PENDING
-                it.localCreationTimestamp = ageLocalTS ?: System.currentTimeMillis()
-                if (request is IncomingSecretShareRequest) {
-                    it.type = GossipRequestType.SECRET
-                    it.requestedInfoStr = request.secretName
-                } else if (request is IncomingRoomKeyRequest) {
-                    it.type = GossipRequestType.KEY
-                    it.requestedInfoStr = request.requestBody?.toJson()
-                }
-            }
-        }
-    }
-
-    override fun storeIncomingGossipingRequests(requests: List) {
-        doRealmTransactionAsync(realmConfiguration) { realm ->
-            requests.forEach { request ->
-                // After a clear cache, we might have a
-                realm.createObject(IncomingGossipingRequestEntity::class.java).let {
-                    it.otherDeviceId = request.deviceId
-                    it.otherUserId = request.userId
-                    it.requestId = request.requestId ?: ""
-                    it.requestState = GossipingRequestState.PENDING
-                    it.localCreationTimestamp = request.localCreationTimestamp ?: System.currentTimeMillis()
-                    if (request is IncomingSecretShareRequest) {
-                        it.type = GossipRequestType.SECRET
-                        it.requestedInfoStr = request.secretName
-                    } else if (request is IncomingRoomKeyRequest) {
-                        it.type = GossipRequestType.KEY
-                        it.requestedInfoStr = request.requestBody?.toJson()
+    override fun updateOutgoingRoomKeyRequiredIndex(requestId: String, newIndex: Int) {
+        doRealmTransaction(realmConfiguration) { realm ->
+            realm.where()
+                    .equalTo(OutgoingKeyRequestEntityFields.REQUEST_ID, requestId)
+                    .findFirst()?.apply {
+                        this.requestedIndex = newIndex
                     }
+        }
+    }
+
+    override fun updateOutgoingRoomKeyReply(roomId: String,
+                                            sessionId: String,
+                                            algorithm: String,
+                                            senderKey: String,
+                                            fromDevice: String?,
+                                            event: Event) {
+        doRealmTransaction(realmConfiguration) { realm ->
+            realm.where()
+                    .equalTo(OutgoingKeyRequestEntityFields.ROOM_ID, roomId)
+                    .equalTo(OutgoingKeyRequestEntityFields.MEGOLM_SESSION_ID, sessionId)
+                    .findAll().firstOrNull { entity ->
+                        entity.toOutgoingKeyRequest().let {
+                            it.requestBody?.senderKey == senderKey &&
+                                    it.requestBody?.algorithm == algorithm
+                        }
+                    }?.apply {
+                        event.senderId?.let { addReply(it, fromDevice, event) }
+                    }
+        }
+    }
+
+    override fun deleteOutgoingRoomKeyRequest(requestId: String) {
+        doRealmTransaction(realmConfiguration) { realm ->
+            realm.where()
+                    .equalTo(OutgoingKeyRequestEntityFields.REQUEST_ID, requestId)
+                    .findFirst()?.deleteOnCascade()
+        }
+    }
+
+    override fun deleteOutgoingRoomKeyRequestInState(state: OutgoingRoomKeyRequestState) {
+        doRealmTransaction(realmConfiguration) { realm ->
+            realm.where()
+                    .equalTo(OutgoingKeyRequestEntityFields.REQUEST_STATE_STR, state.name)
+                    .findAll()
+                    // I delete like this because I want to cascade delete replies?
+                    .onEach { it.deleteOnCascade() }
+        }
+    }
+
+    override fun saveIncomingKeyRequestAuditTrail(
+            requestId: String,
+            roomId: String,
+            sessionId: String,
+            senderKey: String,
+            algorithm: String,
+            fromUser: String,
+            fromDevice: String) {
+        monarchy.writeAsync { realm ->
+            val now = clock.epochMillis()
+            realm.createObject().apply {
+                this.ageLocalTs = now
+                this.type = TrailType.IncomingKeyRequest.name
+                val info = IncomingKeyRequestInfo(
+                        roomId = roomId,
+                        sessionId = sessionId,
+                        senderKey = senderKey,
+                        alg = algorithm,
+                        userId = fromUser,
+                        deviceId = fromDevice,
+                        requestId = requestId
+                )
+                MoshiProvider.providesMoshi().adapter(IncomingKeyRequestInfo::class.java).toJson(info)?.let {
+                    this.contentJson = it
                 }
             }
         }
     }
 
-//    override fun getPendingIncomingSecretShareRequests(): List {
-//        return doRealmQueryAndCopyList(realmConfiguration) {
-//            it.where()
-//                    .findAll()
-//        }.map {
-//            it.toIncomingSecretShareRequest()
-//        }
-//    }
+    override fun saveWithheldAuditTrail(roomId: String,
+                                        sessionId: String,
+                                        senderKey: String,
+                                        algorithm: String,
+                                        code: WithHeldCode,
+                                        userId: String,
+                                        deviceId: String) {
+        monarchy.writeAsync { realm ->
+            val now = clock.epochMillis()
+            realm.createObject().apply {
+                this.ageLocalTs = now
+                this.type = TrailType.OutgoingKeyWithheld.name
+                val info = WithheldInfo(
+                        roomId = roomId,
+                        sessionId = sessionId,
+                        senderKey = senderKey,
+                        alg = algorithm,
+                        code = code,
+                        userId = userId,
+                        deviceId = deviceId
+                )
+                MoshiProvider.providesMoshi().adapter(WithheldInfo::class.java).toJson(info)?.let {
+                    this.contentJson = it
+                }
+            }
+        }
+    }
+
+    override fun saveForwardKeyAuditTrail(roomId: String,
+                                          sessionId: String,
+                                          senderKey: String,
+                                          algorithm: String,
+                                          userId: String,
+                                          deviceId: String,
+                                          chainIndex: Long?) {
+        saveForwardKeyTrail(roomId, sessionId, senderKey, algorithm, userId, deviceId, chainIndex, false)
+    }
+
+    override fun saveIncomingForwardKeyAuditTrail(roomId: String,
+                                                  sessionId: String,
+                                                  senderKey: String,
+                                                  algorithm: String,
+                                                  userId: String,
+                                                  deviceId: String,
+                                                  chainIndex: Long?) {
+        saveForwardKeyTrail(roomId, sessionId, senderKey, algorithm, userId, deviceId, chainIndex, true)
+    }
+
+    private fun saveForwardKeyTrail(roomId: String,
+                                    sessionId: String,
+                                    senderKey: String,
+                                    algorithm: String,
+                                    userId: String,
+                                    deviceId: String,
+                                    chainIndex: Long?,
+                                    incoming: Boolean
+    ) {
+        monarchy.writeAsync { realm ->
+            val now = clock.epochMillis()
+            realm.createObject().apply {
+                this.ageLocalTs = now
+                this.type = if (incoming) TrailType.IncomingKeyForward.name else TrailType.OutgoingKeyForward.name
+                val info = ForwardInfo(
+                        roomId = roomId,
+                        sessionId = sessionId,
+                        senderKey = senderKey,
+                        alg = algorithm,
+                        userId = userId,
+                        deviceId = deviceId,
+                        chainIndex = chainIndex
+                )
+                MoshiProvider.providesMoshi().adapter(ForwardInfo::class.java).toJson(info)?.let {
+                    this.contentJson = it
+                }
+            }
+        }
+    }
 
     /* ==========================================================================================
      * Cross Signing
@@ -1503,45 +1468,45 @@ internal class RealmCryptoStore @Inject constructor(
         }
     }
 
-    override fun getOutgoingRoomKeyRequests(): List {
+    override fun getOutgoingRoomKeyRequests(): List {
         return monarchy.fetchAllMappedSync({ realm ->
             realm
-                    .where(OutgoingGossipingRequestEntity::class.java)
-                    .equalTo(OutgoingGossipingRequestEntityFields.TYPE_STR, GossipRequestType.KEY.name)
+                    .where(OutgoingKeyRequestEntity::class.java)
         }, { entity ->
-            entity.toOutgoingGossipingRequest() as? OutgoingRoomKeyRequest
+            entity.toOutgoingKeyRequest()
         })
                 .filterNotNull()
     }
 
-    override fun getOutgoingSecretKeyRequests(): List {
+    override fun getOutgoingRoomKeyRequests(inStates: Set): List {
         return monarchy.fetchAllMappedSync({ realm ->
             realm
-                    .where(OutgoingGossipingRequestEntity::class.java)
-                    .equalTo(OutgoingGossipingRequestEntityFields.TYPE_STR, GossipRequestType.SECRET.name)
+                    .where(OutgoingKeyRequestEntity::class.java)
+                    .`in`(OutgoingKeyRequestEntityFields.REQUEST_STATE_STR, inStates.map { it.name }.toTypedArray())
         }, { entity ->
-            entity.toOutgoingGossipingRequest() as? OutgoingSecretRequest
+            entity.toOutgoingKeyRequest()
         })
                 .filterNotNull()
     }
 
-    override fun getOutgoingRoomKeyRequestsPaged(): LiveData> {
+    override fun getOutgoingRoomKeyRequestsPaged(): LiveData> {
         val realmDataSourceFactory = monarchy.createDataSourceFactory { realm ->
             realm
-                    .where(OutgoingGossipingRequestEntity::class.java)
-                    .equalTo(OutgoingGossipingRequestEntityFields.TYPE_STR, GossipRequestType.KEY.name)
+                    .where(OutgoingKeyRequestEntity::class.java)
         }
         val dataSourceFactory = realmDataSourceFactory.map {
-            it.toOutgoingGossipingRequest() as? OutgoingRoomKeyRequest
-                    ?: OutgoingRoomKeyRequest(requestBody = null, requestId = "?", recipients = emptyMap(), state = OutgoingGossipingRequestState.CANCELLED)
+            it.toOutgoingKeyRequest()
         }
-        val trail = monarchy.findAllPagedWithChanges(realmDataSourceFactory,
-                LivePagedListBuilder(dataSourceFactory,
+        val trail = monarchy.findAllPagedWithChanges(
+                realmDataSourceFactory,
+                LivePagedListBuilder(
+                        dataSourceFactory,
                         PagedList.Config.Builder()
                                 .setPageSize(20)
                                 .setEnablePlaceholders(false)
                                 .setPrefetchDistance(1)
-                                .build())
+                                .build()
+                )
         )
         return trail
     }
@@ -1702,33 +1667,27 @@ internal class RealmCryptoStore @Inject constructor(
     }
 
     /**
-     * Some entries in the DB can get a bit out of control with time
-     * So we need to tidy up a bit
+     * Some entries in the DB can get a bit out of control with time.
+     * So we need to tidy up a bit.
      */
     override fun tidyUpDataBase() {
-        val prevWeekTs = System.currentTimeMillis() - 7 * 24 * 60 * 60 * 1_000
+        val prevWeekTs = clock.epochMillis() - 7 * 24 * 60 * 60 * 1_000
         doRealmTransaction(realmConfiguration) { realm ->
 
-            // Only keep one week history
-            realm.where()
-                    .lessThan(IncomingGossipingRequestEntityFields.LOCAL_CREATION_TIMESTAMP, prevWeekTs)
+            // Clean the old ones?
+            realm.where()
+                    .lessThan(OutgoingKeyRequestEntityFields.CREATION_TIME_STAMP, prevWeekTs)
                     .findAll()
-                    .also { Timber.i("## Crypto Clean up ${it.size} IncomingGossipingRequestEntity") }
+                    .also { Timber.i("## Crypto Clean up ${it.size} OutgoingKeyRequestEntity") }
                     .deleteAllFromRealm()
 
-            // Clean the cancelled ones?
-            realm.where()
-                    .equalTo(OutgoingGossipingRequestEntityFields.REQUEST_STATE_STR, OutgoingGossipingRequestState.CANCELLED.name)
-                    .equalTo(OutgoingGossipingRequestEntityFields.TYPE_STR, GossipRequestType.KEY.name)
-                    .findAll()
-                    .also { Timber.i("## Crypto Clean up ${it.size} OutgoingGossipingRequestEntity") }
-                    .deleteAllFromRealm()
+            // Only keep one month history
 
-            // Only keep one week history
-            realm.where()
-                    .lessThan(GossipingEventEntityFields.AGE_LOCAL_TS, prevWeekTs)
+            val prevMonthTs = clock.epochMillis() - 4 * 7 * 24 * 60 * 60 * 1_000L
+            realm.where()
+                    .lessThan(AuditTrailEntityFields.AGE_LOCAL_TS, prevMonthTs)
                     .findAll()
-                    .also { Timber.i("## Crypto Clean up ${it.size} GossipingEventEntityFields") }
+                    .also { Timber.i("## Crypto Clean up ${it.size} AuditTrailEntity") }
                     .deleteAllFromRealm()
 
             // Can we do something for WithHeldSessionEntity?
@@ -1736,7 +1695,7 @@ internal class RealmCryptoStore @Inject constructor(
     }
 
     /**
-     * Prints out database info
+     * Prints out database info.
      */
     override fun logDbUsageInfo() {
         RealmDebugTools(realmConfiguration).logInfo("Crypto")
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/RealmCryptoStoreMigration.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/RealmCryptoStoreMigration.kt
index cac6499486..02c2a27dec 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/RealmCryptoStoreMigration.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/RealmCryptoStoreMigration.kt
@@ -33,13 +33,17 @@ import org.matrix.android.sdk.internal.crypto.store.db.migration.MigrateCryptoTo
 import org.matrix.android.sdk.internal.crypto.store.db.migration.MigrateCryptoTo013
 import org.matrix.android.sdk.internal.crypto.store.db.migration.MigrateCryptoTo014
 import org.matrix.android.sdk.internal.crypto.store.db.migration.MigrateCryptoTo015
+import org.matrix.android.sdk.internal.crypto.store.db.migration.MigrateCryptoTo016
+import org.matrix.android.sdk.internal.util.time.Clock
 import timber.log.Timber
 import javax.inject.Inject
 
-internal class RealmCryptoStoreMigration @Inject constructor() : RealmMigration {
+internal class RealmCryptoStoreMigration @Inject constructor(
+        private val clock: Clock,
+) : RealmMigration {
     /**
-     * Forces all RealmCryptoStoreMigration instances to be equal
-     * Avoids Realm throwing when multiple instances of the migration are set
+     * Forces all RealmCryptoStoreMigration instances to be equal.
+     * Avoids Realm throwing when multiple instances of the migration are set.
      */
     override fun equals(other: Any?) = other is RealmCryptoStoreMigration
     override fun hashCode() = 5000
@@ -47,7 +51,7 @@ internal class RealmCryptoStoreMigration @Inject constructor() : RealmMigration
     // 0, 1, 2: legacy Riot-Android
     // 3: migrate to RiotX schema
     // 4, 5, 6, 7, 8, 9: migrations from RiotX (which was previously 1, 2, 3, 4, 5, 6)
-    val schemaVersion = 15L
+    val schemaVersion = 16L
 
     override fun migrate(realm: DynamicRealm, oldVersion: Long, newVersion: Long) {
         Timber.d("Migrating Realm Crypto from $oldVersion to $newVersion")
@@ -59,7 +63,7 @@ internal class RealmCryptoStoreMigration @Inject constructor() : RealmMigration
         if (oldVersion < 5) MigrateCryptoTo005(realm).perform()
         if (oldVersion < 6) MigrateCryptoTo006(realm).perform()
         if (oldVersion < 7) MigrateCryptoTo007(realm).perform()
-        if (oldVersion < 8) MigrateCryptoTo008(realm).perform()
+        if (oldVersion < 8) MigrateCryptoTo008(realm, clock).perform()
         if (oldVersion < 9) MigrateCryptoTo009(realm).perform()
         if (oldVersion < 10) MigrateCryptoTo010(realm).perform()
         if (oldVersion < 11) MigrateCryptoTo011(realm).perform()
@@ -67,5 +71,6 @@ internal class RealmCryptoStoreMigration @Inject constructor() : RealmMigration
         if (oldVersion < 13) MigrateCryptoTo013(realm).perform()
         if (oldVersion < 14) MigrateCryptoTo014(realm).perform()
         if (oldVersion < 15) MigrateCryptoTo015(realm).perform()
+        if (oldVersion < 16) MigrateCryptoTo016(realm).perform()
     }
 }
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/RealmCryptoStoreModule.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/RealmCryptoStoreModule.kt
index a2f2f8e97a..6696cf8281 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/RealmCryptoStoreModule.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/RealmCryptoStoreModule.kt
@@ -17,28 +17,29 @@
 package org.matrix.android.sdk.internal.crypto.store.db
 
 import io.realm.annotations.RealmModule
+import org.matrix.android.sdk.internal.crypto.store.db.model.AuditTrailEntity
 import org.matrix.android.sdk.internal.crypto.store.db.model.CrossSigningInfoEntity
 import org.matrix.android.sdk.internal.crypto.store.db.model.CryptoMetadataEntity
 import org.matrix.android.sdk.internal.crypto.store.db.model.CryptoRoomEntity
 import org.matrix.android.sdk.internal.crypto.store.db.model.DeviceInfoEntity
-import org.matrix.android.sdk.internal.crypto.store.db.model.GossipingEventEntity
-import org.matrix.android.sdk.internal.crypto.store.db.model.IncomingGossipingRequestEntity
 import org.matrix.android.sdk.internal.crypto.store.db.model.KeyInfoEntity
+import org.matrix.android.sdk.internal.crypto.store.db.model.KeyRequestReplyEntity
 import org.matrix.android.sdk.internal.crypto.store.db.model.KeysBackupDataEntity
 import org.matrix.android.sdk.internal.crypto.store.db.model.MyDeviceLastSeenInfoEntity
 import org.matrix.android.sdk.internal.crypto.store.db.model.OlmInboundGroupSessionEntity
 import org.matrix.android.sdk.internal.crypto.store.db.model.OlmSessionEntity
 import org.matrix.android.sdk.internal.crypto.store.db.model.OutboundGroupSessionInfoEntity
-import org.matrix.android.sdk.internal.crypto.store.db.model.OutgoingGossipingRequestEntity
+import org.matrix.android.sdk.internal.crypto.store.db.model.OutgoingKeyRequestEntity
 import org.matrix.android.sdk.internal.crypto.store.db.model.SharedSessionEntity
 import org.matrix.android.sdk.internal.crypto.store.db.model.TrustLevelEntity
 import org.matrix.android.sdk.internal.crypto.store.db.model.UserEntity
 import org.matrix.android.sdk.internal.crypto.store.db.model.WithHeldSessionEntity
 
 /**
- * Realm module for Crypto store classes
+ * Realm module for Crypto store classes.
  */
-@RealmModule(library = true,
+@RealmModule(
+        library = true,
         classes = [
             CryptoMetadataEntity::class,
             CryptoRoomEntity::class,
@@ -50,12 +51,13 @@ import org.matrix.android.sdk.internal.crypto.store.db.model.WithHeldSessionEnti
             KeyInfoEntity::class,
             CrossSigningInfoEntity::class,
             TrustLevelEntity::class,
-            GossipingEventEntity::class,
-            IncomingGossipingRequestEntity::class,
-            OutgoingGossipingRequestEntity::class,
+            AuditTrailEntity::class,
+            OutgoingKeyRequestEntity::class,
+            KeyRequestReplyEntity::class,
             MyDeviceLastSeenInfoEntity::class,
             WithHeldSessionEntity::class,
             SharedSessionEntity::class,
             OutboundGroupSessionInfoEntity::class
-        ])
+        ]
+)
 internal class RealmCryptoStoreModule
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/mapper/CrossSigningKeysMapper.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/mapper/CrossSigningKeysMapper.kt
index 5e4b9b96da..8b7bf9c26b 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/mapper/CrossSigningKeysMapper.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/mapper/CrossSigningKeysMapper.kt
@@ -27,11 +27,13 @@ import javax.inject.Inject
 
 internal class CrossSigningKeysMapper @Inject constructor(moshi: Moshi) {
 
-    private val signaturesAdapter = moshi.adapter>>(Types.newParameterizedType(
-            Map::class.java,
-            String::class.java,
-            Any::class.java
-    ))
+    private val signaturesAdapter = moshi.adapter>>(
+            Types.newParameterizedType(
+                    Map::class.java,
+                    String::class.java,
+                    Any::class.java
+            )
+    )
 
     fun update(keyInfo: KeyInfoEntity, cryptoCrossSigningKey: CryptoCrossSigningKey) {
         // update signatures?
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/migration/MigrateCryptoTo004.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/migration/MigrateCryptoTo004.kt
index 52d0124c2b..e5bdd2aa9b 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/migration/MigrateCryptoTo004.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/migration/MigrateCryptoTo004.kt
@@ -72,16 +72,20 @@ internal class MigrateCryptoTo004(realm: DynamicRealm) : RealmMigrator(realm, 4)
                 ?.addField(CryptoMetadataEntityFields.X_SIGN_SELF_SIGNED_PRIVATE_KEY, String::class.java)
 
         val moshi = Moshi.Builder().add(SerializeNulls.JSON_ADAPTER_FACTORY).build()
-        val listMigrationAdapter = moshi.adapter>(Types.newParameterizedType(
-                List::class.java,
-                String::class.java,
-                Any::class.java
-        ))
-        val mapMigrationAdapter = moshi.adapter(Types.newParameterizedType(
-                Map::class.java,
-                String::class.java,
-                Any::class.java
-        ))
+        val listMigrationAdapter = moshi.adapter>(
+                Types.newParameterizedType(
+                        List::class.java,
+                        String::class.java,
+                        Any::class.java
+                )
+        )
+        val mapMigrationAdapter = moshi.adapter(
+                Types.newParameterizedType(
+                        Map::class.java,
+                        String::class.java,
+                        Any::class.java
+                )
+        )
 
         realm.schema.get("DeviceInfoEntity")
                 ?.addField(DeviceInfoEntityFields.USER_ID, String::class.java)
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/migration/MigrateCryptoTo005.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/migration/MigrateCryptoTo005.kt
index e1d7598767..8ec2932a8f 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/migration/MigrateCryptoTo005.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/migration/MigrateCryptoTo005.kt
@@ -17,9 +17,6 @@
 package org.matrix.android.sdk.internal.crypto.store.db.migration
 
 import io.realm.DynamicRealm
-import org.matrix.android.sdk.internal.crypto.store.db.model.GossipingEventEntityFields
-import org.matrix.android.sdk.internal.crypto.store.db.model.IncomingGossipingRequestEntityFields
-import org.matrix.android.sdk.internal.crypto.store.db.model.OutgoingGossipingRequestEntityFields
 import org.matrix.android.sdk.internal.util.database.RealmMigrator
 
 internal class MigrateCryptoTo005(realm: DynamicRealm) : RealmMigrator(realm, 5) {
@@ -29,38 +26,37 @@ internal class MigrateCryptoTo005(realm: DynamicRealm) : RealmMigrator(realm, 5)
         realm.schema.remove("IncomingRoomKeyRequestEntity")
 
         // Not need to migrate existing request, just start fresh?
-
         realm.schema.create("GossipingEventEntity")
-                .addField(GossipingEventEntityFields.TYPE, String::class.java)
-                .addIndex(GossipingEventEntityFields.TYPE)
-                .addField(GossipingEventEntityFields.CONTENT, String::class.java)
-                .addField(GossipingEventEntityFields.SENDER, String::class.java)
-                .addIndex(GossipingEventEntityFields.SENDER)
-                .addField(GossipingEventEntityFields.DECRYPTION_RESULT_JSON, String::class.java)
-                .addField(GossipingEventEntityFields.DECRYPTION_ERROR_CODE, String::class.java)
-                .addField(GossipingEventEntityFields.AGE_LOCAL_TS, Long::class.java)
-                .setNullable(GossipingEventEntityFields.AGE_LOCAL_TS, true)
-                .addField(GossipingEventEntityFields.SEND_STATE_STR, String::class.java)
+                .addField("type", String::class.java)
+                .addIndex("type")
+                .addField("content", String::class.java)
+                .addField("sender", String::class.java)
+                .addIndex("sender")
+                .addField("decryptionResultJson", String::class.java)
+                .addField("decryptionErrorCode", String::class.java)
+                .addField("ageLocalTs", Long::class.java)
+                .setNullable("ageLocalTs", true)
+                .addField("sendStateStr", String::class.java)
 
         realm.schema.create("IncomingGossipingRequestEntity")
-                .addField(IncomingGossipingRequestEntityFields.REQUEST_ID, String::class.java)
-                .addIndex(IncomingGossipingRequestEntityFields.REQUEST_ID)
-                .addField(IncomingGossipingRequestEntityFields.TYPE_STR, String::class.java)
-                .addIndex(IncomingGossipingRequestEntityFields.TYPE_STR)
-                .addField(IncomingGossipingRequestEntityFields.OTHER_USER_ID, String::class.java)
-                .addField(IncomingGossipingRequestEntityFields.REQUESTED_INFO_STR, String::class.java)
-                .addField(IncomingGossipingRequestEntityFields.OTHER_DEVICE_ID, String::class.java)
-                .addField(IncomingGossipingRequestEntityFields.REQUEST_STATE_STR, String::class.java)
-                .addField(IncomingGossipingRequestEntityFields.LOCAL_CREATION_TIMESTAMP, Long::class.java)
-                .setNullable(IncomingGossipingRequestEntityFields.LOCAL_CREATION_TIMESTAMP, true)
+                .addField("requestId", String::class.java)
+                .addIndex("requestId")
+                .addField("typeStr", String::class.java)
+                .addIndex("typeStr")
+                .addField("otherUserId", String::class.java)
+                .addField("requestedInfoStr", String::class.java)
+                .addField("otherDeviceId", String::class.java)
+                .addField("requestStateStr", String::class.java)
+                .addField("localCreationTimestamp", Long::class.java)
+                .setNullable("localCreationTimestamp", true)
 
         realm.schema.create("OutgoingGossipingRequestEntity")
-                .addField(OutgoingGossipingRequestEntityFields.REQUEST_ID, String::class.java)
-                .addIndex(OutgoingGossipingRequestEntityFields.REQUEST_ID)
-                .addField(OutgoingGossipingRequestEntityFields.RECIPIENTS_DATA, String::class.java)
-                .addField(OutgoingGossipingRequestEntityFields.REQUESTED_INFO_STR, String::class.java)
-                .addField(OutgoingGossipingRequestEntityFields.TYPE_STR, String::class.java)
-                .addIndex(OutgoingGossipingRequestEntityFields.TYPE_STR)
-                .addField(OutgoingGossipingRequestEntityFields.REQUEST_STATE_STR, String::class.java)
+                .addField("requestId", String::class.java)
+                .addIndex("requestId")
+                .addField("recipientsData", String::class.java)
+                .addField("requestedInfoStr", String::class.java)
+                .addField("typeStr", String::class.java)
+                .addIndex("typeStr")
+                .addField("requestStateStr", String::class.java)
     }
 }
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/migration/MigrateCryptoTo007.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/migration/MigrateCryptoTo007.kt
index 718b9787d2..0e221e78f3 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/migration/MigrateCryptoTo007.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/migration/MigrateCryptoTo007.kt
@@ -42,7 +42,7 @@ internal class MigrateCryptoTo007(realm: DynamicRealm) : RealmMigrator(realm, 7)
                 val jsonSignatures = crossSigningKeysMapper.serializeSignatures(objectSignatures)
                 it.setString(KeyInfoEntityFields.SIGNATURES, jsonSignatures)
             }
-        } catch (failure: Throwable) {
+        } catch (ignore: Throwable) {
         }
 
         // Migrate frozen classes
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/migration/MigrateCryptoTo008.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/migration/MigrateCryptoTo008.kt
index 785e6a04f4..ad195e6e55 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/migration/MigrateCryptoTo008.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/migration/MigrateCryptoTo008.kt
@@ -21,8 +21,12 @@ import org.matrix.android.sdk.api.extensions.tryOrNull
 import org.matrix.android.sdk.internal.crypto.store.db.model.DeviceInfoEntityFields
 import org.matrix.android.sdk.internal.crypto.store.db.model.MyDeviceLastSeenInfoEntityFields
 import org.matrix.android.sdk.internal.util.database.RealmMigrator
+import org.matrix.android.sdk.internal.util.time.Clock
 
-internal class MigrateCryptoTo008(realm: DynamicRealm) : RealmMigrator(realm, 8) {
+internal class MigrateCryptoTo008(
+        realm: DynamicRealm,
+        private val clock: Clock,
+) : RealmMigrator(realm, 8) {
 
     override fun doMigrate(realm: DynamicRealm) {
         realm.schema.create("MyDeviceLastSeenInfoEntity")
@@ -33,7 +37,7 @@ internal class MigrateCryptoTo008(realm: DynamicRealm) : RealmMigrator(realm, 8)
                 .addField(MyDeviceLastSeenInfoEntityFields.LAST_SEEN_TS, Long::class.java)
                 .setNullable(MyDeviceLastSeenInfoEntityFields.LAST_SEEN_TS, true)
 
-        val now = System.currentTimeMillis()
+        val now = clock.epochMillis()
         realm.schema.get("DeviceInfoEntity")
                 ?.addField(DeviceInfoEntityFields.FIRST_TIME_SEEN_LOCAL_TS, Long::class.java)
                 ?.setNullable(DeviceInfoEntityFields.FIRST_TIME_SEEN_LOCAL_TS, true)
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/migration/MigrateCryptoTo016.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/migration/MigrateCryptoTo016.kt
new file mode 100644
index 0000000000..5a14ebf85a
--- /dev/null
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/migration/MigrateCryptoTo016.kt
@@ -0,0 +1,70 @@
+/*
+ * Copyright (c) 2022 The Matrix.org Foundation C.I.C.
+ *
+ * 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 org.matrix.android.sdk.internal.crypto.store.db.migration
+
+import io.realm.DynamicRealm
+import org.matrix.android.sdk.internal.crypto.store.db.model.AuditTrailEntityFields
+import org.matrix.android.sdk.internal.crypto.store.db.model.CryptoMetadataEntityFields
+import org.matrix.android.sdk.internal.crypto.store.db.model.KeyRequestReplyEntityFields
+import org.matrix.android.sdk.internal.crypto.store.db.model.OutgoingKeyRequestEntityFields
+import org.matrix.android.sdk.internal.util.database.RealmMigrator
+
+internal class MigrateCryptoTo016(realm: DynamicRealm) : RealmMigrator(realm, 16) {
+
+    override fun doMigrate(realm: DynamicRealm) {
+        realm.schema.remove("OutgoingGossipingRequestEntity")
+        realm.schema.remove("IncomingGossipingRequestEntity")
+        realm.schema.remove("GossipingEventEntity")
+
+        // No need to migrate existing request, just start fresh
+
+        val replySchema = realm.schema.create("KeyRequestReplyEntity")
+                .addField(KeyRequestReplyEntityFields.SENDER_ID, String::class.java)
+                .addField(KeyRequestReplyEntityFields.FROM_DEVICE, String::class.java)
+                .addField(KeyRequestReplyEntityFields.EVENT_JSON, String::class.java)
+
+        realm.schema.create("OutgoingKeyRequestEntity")
+                .addField(OutgoingKeyRequestEntityFields.REQUEST_ID, String::class.java)
+                .addIndex(OutgoingKeyRequestEntityFields.REQUEST_ID)
+                .addField(OutgoingKeyRequestEntityFields.MEGOLM_SESSION_ID, String::class.java)
+                .addIndex(OutgoingKeyRequestEntityFields.MEGOLM_SESSION_ID)
+                .addRealmListField(OutgoingKeyRequestEntityFields.REPLIES.`$`, replySchema)
+                .addField(OutgoingKeyRequestEntityFields.RECIPIENTS_DATA, String::class.java)
+                .addField(OutgoingKeyRequestEntityFields.REQUEST_STATE_STR, String::class.java)
+                .addIndex(OutgoingKeyRequestEntityFields.REQUEST_STATE_STR)
+                .addField(OutgoingKeyRequestEntityFields.REQUESTED_INFO_STR, String::class.java)
+                .addField(OutgoingKeyRequestEntityFields.ROOM_ID, String::class.java)
+                .addIndex(OutgoingKeyRequestEntityFields.ROOM_ID)
+                .addField(OutgoingKeyRequestEntityFields.REQUESTED_INDEX, Integer::class.java)
+                .addField(OutgoingKeyRequestEntityFields.CREATION_TIME_STAMP, Long::class.java)
+                .setNullable(OutgoingKeyRequestEntityFields.CREATION_TIME_STAMP, true)
+
+        realm.schema.create("AuditTrailEntity")
+                .addField(AuditTrailEntityFields.AGE_LOCAL_TS, Long::class.java)
+                .setNullable(AuditTrailEntityFields.AGE_LOCAL_TS, true)
+                .addField(AuditTrailEntityFields.CONTENT_JSON, String::class.java)
+                .addField(AuditTrailEntityFields.TYPE, String::class.java)
+                .addIndex(AuditTrailEntityFields.TYPE)
+
+        realm.schema.get("CryptoMetadataEntity")
+                ?.addField(CryptoMetadataEntityFields.GLOBAL_ENABLE_KEY_GOSSIPING, Boolean::class.java)
+                ?.transform {
+                    // set the default value to true
+                    it.setBoolean(CryptoMetadataEntityFields.GLOBAL_ENABLE_KEY_GOSSIPING, true)
+                }
+    }
+}
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/model/AuditTrailEntity.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/model/AuditTrailEntity.kt
new file mode 100644
index 0000000000..2e0e9c8c8b
--- /dev/null
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/model/AuditTrailEntity.kt
@@ -0,0 +1,28 @@
+/*
+ * Copyright 2022 The Matrix.org Foundation C.I.C.
+ *
+ * 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 org.matrix.android.sdk.internal.crypto.store.db.model
+
+import io.realm.RealmObject
+import io.realm.annotations.Index
+
+internal open class AuditTrailEntity(
+        var ageLocalTs: Long? = null,
+        @Index var type: String? = null,
+        var contentJson: String? = null
+) : RealmObject() {
+    companion object
+}
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/model/AuditTrailMapper.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/model/AuditTrailMapper.kt
new file mode 100644
index 0000000000..80ae4a8d0d
--- /dev/null
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/model/AuditTrailMapper.kt
@@ -0,0 +1,82 @@
+/*
+ * Copyright 2022 The Matrix.org Foundation C.I.C.
+ *
+ * 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 org.matrix.android.sdk.internal.crypto.store.db.model
+
+import org.matrix.android.sdk.api.extensions.tryOrNull
+import org.matrix.android.sdk.api.session.crypto.model.AuditTrail
+import org.matrix.android.sdk.api.session.crypto.model.ForwardInfo
+import org.matrix.android.sdk.api.session.crypto.model.IncomingKeyRequestInfo
+import org.matrix.android.sdk.api.session.crypto.model.TrailType
+import org.matrix.android.sdk.api.session.crypto.model.UnknownInfo
+import org.matrix.android.sdk.api.session.crypto.model.WithheldInfo
+import org.matrix.android.sdk.internal.di.MoshiProvider
+
+internal object AuditTrailMapper {
+
+    fun map(entity: AuditTrailEntity): AuditTrail? {
+        val contentJson = entity.contentJson ?: return null
+        return when (entity.type) {
+            TrailType.OutgoingKeyForward.name  -> {
+                val info = tryOrNull {
+                    MoshiProvider.providesMoshi().adapter(ForwardInfo::class.java).fromJson(contentJson)
+                } ?: return null
+                AuditTrail(
+                        ageLocalTs = entity.ageLocalTs ?: 0,
+                        type = TrailType.OutgoingKeyForward,
+                        info = info
+                )
+            }
+            TrailType.OutgoingKeyWithheld.name -> {
+                val info = tryOrNull {
+                    MoshiProvider.providesMoshi().adapter(WithheldInfo::class.java).fromJson(contentJson)
+                } ?: return null
+                AuditTrail(
+                        ageLocalTs = entity.ageLocalTs ?: 0,
+                        type = TrailType.OutgoingKeyWithheld,
+                        info = info
+                )
+            }
+            TrailType.IncomingKeyRequest.name  -> {
+                val info = tryOrNull {
+                    MoshiProvider.providesMoshi().adapter(IncomingKeyRequestInfo::class.java).fromJson(contentJson)
+                } ?: return null
+                AuditTrail(
+                        ageLocalTs = entity.ageLocalTs ?: 0,
+                        type = TrailType.IncomingKeyRequest,
+                        info = info
+                )
+            }
+            TrailType.IncomingKeyForward.name  -> {
+                val info = tryOrNull {
+                    MoshiProvider.providesMoshi().adapter(ForwardInfo::class.java).fromJson(contentJson)
+                } ?: return null
+                AuditTrail(
+                        ageLocalTs = entity.ageLocalTs ?: 0,
+                        type = TrailType.IncomingKeyForward,
+                        info = info
+                )
+            }
+            else                               -> {
+                AuditTrail(
+                        ageLocalTs = entity.ageLocalTs ?: 0,
+                        type = TrailType.Unknown,
+                        info = UnknownInfo
+                )
+            }
+        }
+    }
+}
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/model/CryptoMapper.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/model/CryptoMapper.kt
index c71d5e73a2..ca41930f80 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/model/CryptoMapper.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/model/CryptoMapper.kt
@@ -27,21 +27,27 @@ import timber.log.Timber
 internal object CryptoMapper {
 
     private val moshi = Moshi.Builder().add(SerializeNulls.JSON_ADAPTER_FACTORY).build()
-    private val listMigrationAdapter = moshi.adapter>(Types.newParameterizedType(
-            List::class.java,
-            String::class.java,
-            Any::class.java
-    ))
-    private val mapMigrationAdapter = moshi.adapter(Types.newParameterizedType(
-            Map::class.java,
-            String::class.java,
-            Any::class.java
-    ))
-    private val mapOfStringMigrationAdapter = moshi.adapter>>(Types.newParameterizedType(
-            Map::class.java,
-            String::class.java,
-            Any::class.java
-    ))
+    private val listMigrationAdapter = moshi.adapter>(
+            Types.newParameterizedType(
+                    List::class.java,
+                    String::class.java,
+                    Any::class.java
+            )
+    )
+    private val mapMigrationAdapter = moshi.adapter(
+            Types.newParameterizedType(
+                    Map::class.java,
+                    String::class.java,
+                    Any::class.java
+            )
+    )
+    private val mapOfStringMigrationAdapter = moshi.adapter>>(
+            Types.newParameterizedType(
+                    Map::class.java,
+                    String::class.java,
+                    Any::class.java
+            )
+    )
 
     internal fun mapToEntity(deviceInfo: CryptoDeviceInfo): DeviceInfoEntity {
         return DeviceInfoEntity(primaryKey = DeviceInfoEntity.createPrimaryKey(deviceInfo.userId, deviceInfo.deviceId))
@@ -91,11 +97,13 @@ internal object CryptoMapper {
                 },
                 keys = deviceInfoEntity.keysMapJson?.let {
                     try {
-                        moshi.adapter>(Types.newParameterizedType(
-                                Map::class.java,
-                                String::class.java,
-                                Any::class.java
-                        )).fromJson(it)
+                        moshi.adapter>(
+                                Types.newParameterizedType(
+                                        Map::class.java,
+                                        String::class.java,
+                                        Any::class.java
+                                )
+                        ).fromJson(it)
                     } catch (failure: Throwable) {
                         Timber.e(failure)
                         null
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/model/CryptoMetadataEntity.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/model/CryptoMetadataEntity.kt
index 35ae86db8b..63ed0e537e 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/model/CryptoMetadataEntity.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/model/CryptoMetadataEntity.kt
@@ -33,6 +33,8 @@ internal open class CryptoMetadataEntity(
         var deviceSyncToken: String? = null,
         // Settings for blacklisting unverified devices.
         var globalBlacklistUnverifiedDevices: Boolean = false,
+        // setting to enable or disable key gossiping
+        var globalEnableKeyGossiping: Boolean = true,
         // The keys backup version currently used. Null means no backup.
         var backupVersion: String? = null,
 
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/model/CryptoRoomEntity.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/model/CryptoRoomEntity.kt
index 6167314b5a..114a596964 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/model/CryptoRoomEntity.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/model/CryptoRoomEntity.kt
@@ -31,8 +31,8 @@ internal open class CryptoRoomEntity(
         // a security to ensure that a room will never revert to not encrypted
         // even if a new state event with empty encryption, or state is reset somehow
         var wasEncryptedOnce: Boolean? = false
-        ) :
-    RealmObject() {
+) :
+        RealmObject() {
 
     companion object
 }
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/model/GossipingEventEntity.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/model/GossipingEventEntity.kt
deleted file mode 100644
index a024e092b7..0000000000
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/model/GossipingEventEntity.kt
+++ /dev/null
@@ -1,88 +0,0 @@
-/*
- * Copyright 2020 The Matrix.org Foundation C.I.C.
- *
- * 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 org.matrix.android.sdk.internal.crypto.store.db.model
-
-import com.squareup.moshi.JsonDataException
-import io.realm.RealmObject
-import io.realm.annotations.Index
-import org.matrix.android.sdk.api.session.crypto.MXCryptoError
-import org.matrix.android.sdk.api.session.crypto.model.MXEventDecryptionResult
-import org.matrix.android.sdk.api.session.crypto.model.OlmDecryptionResult
-import org.matrix.android.sdk.api.session.events.model.Event
-import org.matrix.android.sdk.api.session.room.send.SendState
-import org.matrix.android.sdk.internal.database.mapper.ContentMapper
-import org.matrix.android.sdk.internal.di.MoshiProvider
-import timber.log.Timber
-
-/**
- * Keep track of gossiping event received in toDevice messages
- * (room key request, or sss secret sharing, as well as cancellations)
- *
- */
-internal open class GossipingEventEntity(@Index var type: String? = "",
-                                         var content: String? = null,
-                                         @Index var sender: String? = null,
-                                         var decryptionResultJson: String? = null,
-                                         var decryptionErrorCode: String? = null,
-                                         var ageLocalTs: Long? = null) : RealmObject() {
-
-    private var sendStateStr: String = SendState.UNKNOWN.name
-
-    var sendState: SendState
-        get() {
-            return SendState.valueOf(sendStateStr)
-        }
-        set(value) {
-            sendStateStr = value.name
-        }
-
-    companion object
-
-    fun setDecryptionResult(result: MXEventDecryptionResult) {
-        val decryptionResult = OlmDecryptionResult(
-                payload = result.clearEvent,
-                senderKey = result.senderCurve25519Key,
-                keysClaimed = result.claimedEd25519Key?.let { mapOf("ed25519" to it) },
-                forwardingCurve25519KeyChain = result.forwardingCurve25519KeyChain
-        )
-        val adapter = MoshiProvider.providesMoshi().adapter(OlmDecryptionResult::class.java)
-        decryptionResultJson = adapter.toJson(decryptionResult)
-        decryptionErrorCode = null
-    }
-
-    fun toModel(): Event {
-        return Event(
-                type = this.type ?: "",
-                content = ContentMapper.map(this.content),
-                senderId = this.sender
-        ).also {
-            it.ageLocalTs = this.ageLocalTs
-            it.sendState = this.sendState
-            this.decryptionResultJson?.let { json ->
-                try {
-                    it.mxDecryptionResult = MoshiProvider.providesMoshi().adapter(OlmDecryptionResult::class.java).fromJson(json)
-                } catch (t: JsonDataException) {
-                    Timber.e(t, "Failed to parse decryption result")
-                }
-            }
-            // TODO get the full crypto error object
-            it.mCryptoError = this.decryptionErrorCode?.let { errorCode ->
-                MXCryptoError.ErrorType.valueOf(errorCode)
-            }
-        }
-    }
-}
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/model/IncomingGossipingRequestEntity.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/model/IncomingGossipingRequestEntity.kt
deleted file mode 100644
index f05c8853c8..0000000000
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/model/IncomingGossipingRequestEntity.kt
+++ /dev/null
@@ -1,89 +0,0 @@
-/*
- * Copyright 2020 The Matrix.org Foundation C.I.C.
- *
- * 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 org.matrix.android.sdk.internal.crypto.store.db.model
-
-import io.realm.RealmObject
-import io.realm.annotations.Index
-import org.matrix.android.sdk.api.extensions.tryOrNull
-import org.matrix.android.sdk.api.session.crypto.model.GossipingRequestState
-import org.matrix.android.sdk.api.session.crypto.model.IncomingRoomKeyRequest
-import org.matrix.android.sdk.api.session.crypto.model.IncomingSecretShareRequest
-import org.matrix.android.sdk.api.session.crypto.model.RoomKeyRequestBody
-import org.matrix.android.sdk.internal.crypto.GossipRequestType
-import org.matrix.android.sdk.internal.crypto.IncomingShareRequestCommon
-
-internal open class IncomingGossipingRequestEntity(@Index var requestId: String? = "",
-                                                   @Index var typeStr: String? = null,
-                                                   var otherUserId: String? = null,
-                                                   var requestedInfoStr: String? = null,
-                                                   var otherDeviceId: String? = null,
-                                                   var localCreationTimestamp: Long? = null
-) : RealmObject() {
-
-    fun getRequestedSecretName(): String? = if (type == GossipRequestType.SECRET) {
-        requestedInfoStr
-    } else null
-
-    fun getRequestedKeyInfo(): RoomKeyRequestBody? = if (type == GossipRequestType.KEY) {
-        RoomKeyRequestBody.fromJson(requestedInfoStr)
-    } else null
-
-    var type: GossipRequestType
-        get() {
-            return tryOrNull { typeStr?.let { GossipRequestType.valueOf(it) } } ?: GossipRequestType.KEY
-        }
-        set(value) {
-            typeStr = value.name
-        }
-
-    private var requestStateStr: String = GossipingRequestState.NONE.name
-
-    var requestState: GossipingRequestState
-        get() {
-            return tryOrNull { GossipingRequestState.valueOf(requestStateStr) }
-                    ?: GossipingRequestState.NONE
-        }
-        set(value) {
-            requestStateStr = value.name
-        }
-
-    companion object
-
-    fun toIncomingGossipingRequest(): IncomingShareRequestCommon {
-        return when (type) {
-            GossipRequestType.KEY    -> {
-                IncomingRoomKeyRequest(
-                        requestBody = getRequestedKeyInfo(),
-                        deviceId = otherDeviceId,
-                        userId = otherUserId,
-                        requestId = requestId,
-                        state = requestState,
-                        localCreationTimestamp = localCreationTimestamp
-                )
-            }
-            GossipRequestType.SECRET -> {
-                IncomingSecretShareRequest(
-                        secretName = getRequestedSecretName(),
-                        deviceId = otherDeviceId,
-                        userId = otherUserId,
-                        requestId = requestId,
-                        localCreationTimestamp = localCreationTimestamp
-                )
-            }
-        }
-    }
-}
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/model/KeyRequestReplyEntity.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/model/KeyRequestReplyEntity.kt
new file mode 100644
index 0000000000..0c7cf79e78
--- /dev/null
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/model/KeyRequestReplyEntity.kt
@@ -0,0 +1,35 @@
+/*
+ * Copyright 2022 The Matrix.org Foundation C.I.C.
+ *
+ * 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 org.matrix.android.sdk.internal.crypto.store.db.model
+
+import io.realm.RealmObject
+import org.matrix.android.sdk.api.session.events.model.Event
+import org.matrix.android.sdk.internal.di.MoshiProvider
+
+internal open class KeyRequestReplyEntity(
+        var senderId: String? = null,
+        var fromDevice: String? = null,
+        var eventJson: String? = null
+) : RealmObject() {
+    companion object
+
+    fun getEvent(): Event? {
+        return eventJson?.let {
+            MoshiProvider.providesMoshi().adapter(Event::class.java).fromJson(it)
+        }
+    }
+}
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/model/MyDeviceLastSeenInfoEntity.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/model/MyDeviceLastSeenInfoEntity.kt
index 222711f9ac..74a81d5b01 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/model/MyDeviceLastSeenInfoEntity.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/model/MyDeviceLastSeenInfoEntity.kt
@@ -20,13 +20,13 @@ import io.realm.RealmObject
 import io.realm.annotations.PrimaryKey
 
 internal open class MyDeviceLastSeenInfoEntity(
-        /**The device id*/
+        /** The device id. */
         @PrimaryKey var deviceId: String? = null,
-        /** The device display name*/
+        /** The device display name. */
         var displayName: String? = null,
         /** The last time this device has been seen. */
         var lastSeenTs: Long? = null,
-        /** The last ip address*/
+        /** The last ip address. */
         var lastSeenIp: String? = null
 ) : RealmObject() {
 
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/model/OlmInboundGroupSessionEntity.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/model/OlmInboundGroupSessionEntity.kt
index f330e8822a..83671b28d9 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/model/OlmInboundGroupSessionEntity.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/model/OlmInboundGroupSessionEntity.kt
@@ -34,7 +34,7 @@ internal open class OlmInboundGroupSessionEntity(
         var olmInboundGroupSessionData: String? = null,
         // Indicate if the key has been backed up to the homeserver
         var backedUp: Boolean = false) :
-    RealmObject() {
+        RealmObject() {
 
     fun getInboundGroupSession(): OlmInboundGroupSessionWrapper2? {
         return try {
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/model/OlmSessionEntity.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/model/OlmSessionEntity.kt
index 0b69311c57..1a637d76c4 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/model/OlmSessionEntity.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/model/OlmSessionEntity.kt
@@ -30,7 +30,7 @@ internal open class OlmSessionEntity(@PrimaryKey var primaryKey: String = "",
                                      var deviceKey: String? = null,
                                      var olmSessionData: String? = null,
                                      var lastReceivedMessageTs: Long = 0) :
-    RealmObject() {
+        RealmObject() {
 
     fun getOlmSession(): OlmSession? {
         return deserializeFromRealm(olmSessionData)
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/model/OutgoingGossipingRequestEntity.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/model/OutgoingGossipingRequestEntity.kt
deleted file mode 100644
index 0e1278967e..0000000000
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/model/OutgoingGossipingRequestEntity.kt
+++ /dev/null
@@ -1,104 +0,0 @@
-/*
- * Copyright 2020 The Matrix.org Foundation C.I.C.
- *
- * 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 org.matrix.android.sdk.internal.crypto.store.db.model
-
-import com.squareup.moshi.JsonAdapter
-import com.squareup.moshi.Types
-import io.realm.RealmObject
-import io.realm.annotations.Index
-import org.matrix.android.sdk.api.extensions.tryOrNull
-import org.matrix.android.sdk.api.session.crypto.model.OutgoingGossipingRequestState
-import org.matrix.android.sdk.api.session.crypto.model.OutgoingRoomKeyRequest
-import org.matrix.android.sdk.api.session.crypto.model.RoomKeyRequestBody
-import org.matrix.android.sdk.internal.crypto.GossipRequestType
-import org.matrix.android.sdk.internal.crypto.OutgoingGossipingRequest
-import org.matrix.android.sdk.internal.crypto.OutgoingSecretRequest
-import org.matrix.android.sdk.internal.di.MoshiProvider
-
-internal open class OutgoingGossipingRequestEntity(
-        @Index var requestId: String? = null,
-        var recipientsData: String? = null,
-        var requestedInfoStr: String? = null,
-        @Index var typeStr: String? = null
-) : RealmObject() {
-
-    fun getRequestedSecretName(): String? = if (type == GossipRequestType.SECRET) {
-        requestedInfoStr
-    } else null
-
-    fun getRequestedKeyInfo(): RoomKeyRequestBody? = if (type == GossipRequestType.KEY) {
-        RoomKeyRequestBody.fromJson(requestedInfoStr)
-    } else null
-
-    var type: GossipRequestType
-        get() {
-            return tryOrNull { typeStr?.let { GossipRequestType.valueOf(it) } } ?: GossipRequestType.KEY
-        }
-        set(value) {
-            typeStr = value.name
-        }
-
-    private var requestStateStr: String = OutgoingGossipingRequestState.UNSENT.name
-
-    var requestState: OutgoingGossipingRequestState
-        get() {
-            return tryOrNull { OutgoingGossipingRequestState.valueOf(requestStateStr) }
-                    ?: OutgoingGossipingRequestState.UNSENT
-        }
-        set(value) {
-            requestStateStr = value.name
-        }
-
-    companion object {
-
-        private val recipientsDataMapper: JsonAdapter>> =
-                MoshiProvider
-                        .providesMoshi()
-                        .adapter>>(
-                                Types.newParameterizedType(Map::class.java, String::class.java, List::class.java)
-                        )
-    }
-
-    fun toOutgoingGossipingRequest(): OutgoingGossipingRequest {
-        return when (type) {
-            GossipRequestType.KEY    -> {
-                OutgoingRoomKeyRequest(
-                        requestBody = getRequestedKeyInfo(),
-                        recipients = getRecipients().orEmpty(),
-                        requestId = requestId ?: "",
-                        state = requestState
-                )
-            }
-            GossipRequestType.SECRET -> {
-                OutgoingSecretRequest(
-                        secretName = getRequestedSecretName(),
-                        recipients = getRecipients().orEmpty(),
-                        requestId = requestId ?: "",
-                        state = requestState
-                )
-            }
-        }
-    }
-
-    private fun getRecipients(): Map>? {
-        return this.recipientsData?.let { recipientsDataMapper.fromJson(it) }
-    }
-
-    fun setRecipients(recipients: Map>) {
-        this.recipientsData = recipientsDataMapper.toJson(recipients)
-    }
-}
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/model/OutgoingKeyRequestEntity.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/model/OutgoingKeyRequestEntity.kt
new file mode 100644
index 0000000000..7a8ba18809
--- /dev/null
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/model/OutgoingKeyRequestEntity.kt
@@ -0,0 +1,136 @@
+/*
+ * Copyright (c) 2022 The Matrix.org Foundation C.I.C.
+ *
+ * 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 org.matrix.android.sdk.internal.crypto.store.db.model
+
+import com.squareup.moshi.JsonAdapter
+import com.squareup.moshi.Types
+import io.realm.RealmList
+import io.realm.RealmObject
+import io.realm.annotations.Index
+import org.matrix.android.sdk.api.extensions.tryOrNull
+import org.matrix.android.sdk.api.session.crypto.OutgoingKeyRequest
+import org.matrix.android.sdk.api.session.crypto.OutgoingRoomKeyRequestState
+import org.matrix.android.sdk.api.session.crypto.RequestReply
+import org.matrix.android.sdk.api.session.crypto.RequestResult
+import org.matrix.android.sdk.api.session.crypto.model.RoomKeyRequestBody
+import org.matrix.android.sdk.api.session.events.model.Event
+import org.matrix.android.sdk.api.session.events.model.EventType
+import org.matrix.android.sdk.api.session.events.model.content.RoomKeyWithHeldContent
+import org.matrix.android.sdk.api.session.events.model.toModel
+import org.matrix.android.sdk.internal.di.MoshiProvider
+
+internal open class OutgoingKeyRequestEntity(
+        @Index var requestId: String? = null,
+        var requestedIndex: Int? = null,
+        var recipientsData: String? = null,
+        var requestedInfoStr: String? = null,
+        var creationTimeStamp: Long? = null,
+        // de-normalization for better query (if not have to query all and parse json)
+        @Index var roomId: String? = null,
+        @Index var megolmSessionId: String? = null,
+
+        var replies: RealmList = RealmList()
+) : RealmObject() {
+
+    @Index private var requestStateStr: String = OutgoingRoomKeyRequestState.UNSENT.name
+
+    companion object {
+
+        private val recipientsDataMapper: JsonAdapter>> =
+                MoshiProvider
+                        .providesMoshi()
+                        .adapter(
+                                Types.newParameterizedType(Map::class.java, String::class.java, List::class.java)
+                        )
+    }
+
+    private fun getRequestedKeyInfo(): RoomKeyRequestBody? = RoomKeyRequestBody.fromJson(requestedInfoStr)
+
+    fun setRequestBody(body: RoomKeyRequestBody) {
+        requestedInfoStr = body.toJson()
+        roomId = body.roomId
+        megolmSessionId = body.sessionId
+    }
+
+    var requestState: OutgoingRoomKeyRequestState
+        get() {
+            return tryOrNull { OutgoingRoomKeyRequestState.valueOf(requestStateStr) }
+                    ?: OutgoingRoomKeyRequestState.UNSENT
+        }
+        set(value) {
+            requestStateStr = value.name
+        }
+
+    private fun getRecipients(): Map>? {
+        return this.recipientsData?.let { recipientsDataMapper.fromJson(it) }
+    }
+
+    fun setRecipients(recipients: Map>) {
+        this.recipientsData = recipientsDataMapper.toJson(recipients)
+    }
+
+    fun addReply(userId: String, fromDevice: String?, event: Event) {
+        val newReply = KeyRequestReplyEntity(
+                senderId = userId,
+                fromDevice = fromDevice,
+                eventJson = MoshiProvider.providesMoshi().adapter(Event::class.java).toJson(event)
+        )
+        replies.add(newReply)
+    }
+
+    fun toOutgoingKeyRequest(): OutgoingKeyRequest {
+        return OutgoingKeyRequest(
+                requestBody = getRequestedKeyInfo(),
+                recipients = getRecipients().orEmpty(),
+                requestId = requestId ?: "",
+                fromIndex = requestedIndex ?: 0,
+                state = requestState,
+                results = replies.mapNotNull { entity ->
+                    val userId = entity.senderId ?: return@mapNotNull null
+                    val result = entity.eventJson?.let {
+                        MoshiProvider.providesMoshi().adapter(Event::class.java).fromJson(it)
+                    }?.let { event ->
+                        eventToResult(event)
+                    } ?: return@mapNotNull null
+                    RequestReply(
+                            userId = userId,
+                            fromDevice = entity.fromDevice,
+                            result = result
+                    )
+                }
+        )
+    }
+
+    private fun eventToResult(event: Event): RequestResult? {
+        return when (event.getClearType()) {
+            EventType.ROOM_KEY_WITHHELD  -> {
+                event.content.toModel()?.code?.let {
+                    RequestResult.Failure(it)
+                }
+            }
+            EventType.FORWARDED_ROOM_KEY -> {
+                RequestResult.Success((event.content?.get("chain_index") as? Number)?.toInt() ?: 0)
+            }
+            else                         -> null
+        }
+    }
+}
+
+internal fun OutgoingKeyRequestEntity.deleteOnCascade() {
+    replies.deleteAllFromRealm()
+    deleteFromRealm()
+}
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/query/CryptoRoomEntityQueries.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/query/CryptoRoomEntityQueries.kt
index 5750cc1f67..68c4974071 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/query/CryptoRoomEntityQueries.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/query/CryptoRoomEntityQueries.kt
@@ -23,14 +23,14 @@ import org.matrix.android.sdk.internal.crypto.store.db.model.CryptoRoomEntity
 import org.matrix.android.sdk.internal.crypto.store.db.model.CryptoRoomEntityFields
 
 /**
- * Get or create a room
+ * Get or create a room.
  */
 internal fun CryptoRoomEntity.Companion.getOrCreate(realm: Realm, roomId: String): CryptoRoomEntity {
     return getById(realm, roomId) ?: realm.createObject(roomId)
 }
 
 /**
- * Get a room
+ * Get a room.
  */
 internal fun CryptoRoomEntity.Companion.getById(realm: Realm, roomId: String): CryptoRoomEntity? {
     return realm.where()
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/query/DeviceInfoEntityQueries.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/query/DeviceInfoEntityQueries.kt
index c9523d63ce..0a922e79bc 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/query/DeviceInfoEntityQueries.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/query/DeviceInfoEntityQueries.kt
@@ -24,7 +24,7 @@ import org.matrix.android.sdk.internal.crypto.store.db.model.DeviceInfoEntityFie
 import org.matrix.android.sdk.internal.crypto.store.db.model.createPrimaryKey
 
 /**
- * Get or create a device info
+ * Get or create a device info.
  */
 internal fun DeviceInfoEntity.Companion.getOrCreate(realm: Realm, userId: String, deviceId: String): DeviceInfoEntity {
     val key = DeviceInfoEntity.createPrimaryKey(userId, deviceId)
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/query/UserEntitiesQueries.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/query/UserEntitiesQueries.kt
index 5a3b8e5397..73c3997439 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/query/UserEntitiesQueries.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/query/UserEntitiesQueries.kt
@@ -24,7 +24,7 @@ import org.matrix.android.sdk.internal.crypto.store.db.model.UserEntityFields
 import org.matrix.android.sdk.internal.crypto.store.db.model.deleteOnCascade
 
 /**
- * Get or create a user
+ * Get or create a user.
  */
 internal fun UserEntity.Companion.getOrCreate(realm: Realm, userId: String): UserEntity {
     return realm.where()
@@ -34,7 +34,7 @@ internal fun UserEntity.Companion.getOrCreate(realm: Realm, userId: String): Use
 }
 
 /**
- * Delete a user
+ * Delete a user.
  */
 internal fun UserEntity.Companion.delete(realm: Realm, userId: String) {
     realm.where()
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/tasks/DeleteDeviceTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/tasks/DeleteDeviceTask.kt
index ca04bac5d5..0a77d33acc 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/tasks/DeleteDeviceTask.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/tasks/DeleteDeviceTask.kt
@@ -18,6 +18,7 @@ package org.matrix.android.sdk.internal.crypto.tasks
 
 import org.matrix.android.sdk.api.auth.UIABaseAuth
 import org.matrix.android.sdk.api.auth.UserInteractiveAuthInterceptor
+import org.matrix.android.sdk.api.session.uia.UiaResult
 import org.matrix.android.sdk.internal.auth.registration.handleUIA
 import org.matrix.android.sdk.internal.crypto.api.CryptoApi
 import org.matrix.android.sdk.internal.crypto.model.rest.DeleteDeviceParams
@@ -47,13 +48,13 @@ internal class DefaultDeleteDeviceTask @Inject constructor(
             }
         } catch (throwable: Throwable) {
             if (params.userInteractiveAuthInterceptor == null ||
-                    !handleUIA(
+                    handleUIA(
                             failure = throwable,
                             interceptor = params.userInteractiveAuthInterceptor,
                             retryBlock = { authUpdate ->
                                 execute(params.copy(userAuthParam = authUpdate))
                             }
-                    )
+                    ) != UiaResult.SUCCESS
             ) {
                 Timber.d("## UIA: propagate failure")
                 throw throwable
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/tasks/EncryptEventTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/tasks/EncryptEventTask.kt
index 1e395796a9..394c618968 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/tasks/EncryptEventTask.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/tasks/EncryptEventTask.kt
@@ -15,6 +15,7 @@
  */
 package org.matrix.android.sdk.internal.crypto.tasks
 
+import dagger.Lazy
 import org.matrix.android.sdk.api.crypto.MXCRYPTO_ALGORITHM_MEGOLM
 import org.matrix.android.sdk.api.session.crypto.CryptoService
 import org.matrix.android.sdk.api.session.crypto.model.MXEncryptEventContentResult
@@ -39,7 +40,7 @@ internal interface EncryptEventTask : Task {
 
 internal class DefaultEncryptEventTask @Inject constructor(
         private val localEchoRepository: LocalEchoRepository,
-        private val cryptoService: CryptoService
+        private val cryptoService: Lazy
 ) : EncryptEventTask {
     override suspend fun execute(params: EncryptEventTask.Params): Event {
         // don't want to wait for any query
@@ -59,7 +60,7 @@ internal class DefaultEncryptEventTask @Inject constructor(
 //        try {
         // let it throws
         awaitCallback {
-            cryptoService.encryptEventContent(localMutableContent, localEvent.type, params.roomId, it)
+            cryptoService.get().encryptEventContent(localMutableContent, localEvent.type, params.roomId, it)
         }.let { result ->
             val modifiedContent = HashMap(result.eventContent)
             params.keepKeys?.forEach { toKeep ->
@@ -80,7 +81,7 @@ internal class DefaultEncryptEventTask @Inject constructor(
                         ).toContent(),
                         forwardingCurve25519KeyChain = emptyList(),
                         senderCurve25519Key = result.eventContent["sender_key"] as? String,
-                        claimedEd25519Key = cryptoService.getMyDevice().fingerprint()
+                        claimedEd25519Key = cryptoService.get().getMyDevice().fingerprint()
                 )
             } else {
                 null
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/tasks/InitializeCrossSigningTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/tasks/InitializeCrossSigningTask.kt
index eefdd25044..53190c43ff 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/tasks/InitializeCrossSigningTask.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/tasks/InitializeCrossSigningTask.kt
@@ -20,6 +20,7 @@ import dagger.Lazy
 import org.matrix.android.sdk.api.auth.UserInteractiveAuthInterceptor
 import org.matrix.android.sdk.api.session.crypto.crosssigning.CryptoCrossSigningKey
 import org.matrix.android.sdk.api.session.crypto.crosssigning.KeyUsage
+import org.matrix.android.sdk.api.session.uia.UiaResult
 import org.matrix.android.sdk.api.util.toBase64NoPadding
 import org.matrix.android.sdk.internal.auth.registration.handleUIA
 import org.matrix.android.sdk.internal.crypto.MXOlmDevice
@@ -126,13 +127,13 @@ internal class DefaultInitializeCrossSigningTask @Inject constructor(
                 uploadSigningKeysTask.execute(uploadSigningKeysParams)
             } catch (failure: Throwable) {
                 if (params.interactiveAuthInterceptor == null ||
-                        !handleUIA(
+                        handleUIA(
                                 failure = failure,
                                 interceptor = params.interactiveAuthInterceptor,
                                 retryBlock = { authUpdate ->
                                     uploadSigningKeysTask.execute(uploadSigningKeysParams.copy(userAuthParam = authUpdate))
                                 }
-                        )
+                        ) != UiaResult.SUCCESS
                 ) {
                     Timber.d("## UIA: propagate failure")
                     throw failure
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/tasks/SendEventTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/tasks/SendEventTask.kt
index bdfe818c62..50efe51bc1 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/tasks/SendEventTask.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/tasks/SendEventTask.kt
@@ -15,6 +15,7 @@
  */
 package org.matrix.android.sdk.internal.crypto.tasks
 
+import org.matrix.android.sdk.api.extensions.tryOrNull
 import org.matrix.android.sdk.api.session.events.model.Event
 import org.matrix.android.sdk.api.session.room.send.SendState
 import org.matrix.android.sdk.internal.network.GlobalErrorReceiver
@@ -46,7 +47,9 @@ internal class DefaultSendEventTask @Inject constructor(
             params.event.roomId
                     ?.takeIf { params.encrypt }
                     ?.let { roomId ->
-                        loadRoomMembersTask.execute(LoadRoomMembersTask.Params(roomId))
+                        tryOrNull {
+                            loadRoomMembersTask.execute(LoadRoomMembersTask.Params(roomId))
+                        }
                     }
 
             val event = handleEncryption(params)
@@ -66,6 +69,7 @@ internal class DefaultSendEventTask @Inject constructor(
             }
         } catch (e: Throwable) {
 //            localEchoRepository.updateSendState(params.event.eventId!!, SendState.UNDELIVERED)
+            Timber.w(e, "Unable to send the Event")
             throw e
         }
     }
@@ -73,11 +77,13 @@ internal class DefaultSendEventTask @Inject constructor(
     @Throws
     private suspend fun handleEncryption(params: SendEventTask.Params): Event {
         if (params.encrypt && !params.event.isEncrypted()) {
-            return encryptEventTask.execute(EncryptEventTask.Params(
-                    params.event.roomId ?: "",
-                    params.event,
-                    listOf("m.relates_to")
-            ))
+            return encryptEventTask.execute(
+                    EncryptEventTask.Params(
+                            params.event.roomId ?: "",
+                            params.event,
+                            listOf("m.relates_to")
+                    )
+            )
         }
         return params.event
     }
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/tasks/SendVerificationMessageTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/tasks/SendVerificationMessageTask.kt
index c4a6ba27d6..7c52c62d92 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/tasks/SendVerificationMessageTask.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/tasks/SendVerificationMessageTask.kt
@@ -47,7 +47,7 @@ internal class DefaultSendVerificationMessageTask @Inject constructor(
             localEchoRepository.updateSendState(localId, event.roomId, SendState.SENDING)
             val response = executeRequest(globalErrorReceiver) {
                 roomAPI.send(
-                        localId,
+                        txId = localId,
                         roomId = event.roomId ?: "",
                         content = event.content,
                         eventType = event.type ?: ""
@@ -64,11 +64,13 @@ internal class DefaultSendVerificationMessageTask @Inject constructor(
     private suspend fun handleEncryption(params: SendVerificationMessageTask.Params): Event {
         if (cryptoSessionInfoProvider.isRoomEncrypted(params.event.roomId ?: "")) {
             try {
-                return encryptEventTask.execute(EncryptEventTask.Params(
-                        params.event.roomId ?: "",
-                        params.event,
-                        listOf("m.relates_to")
-                ))
+                return encryptEventTask.execute(
+                        EncryptEventTask.Params(
+                                params.event.roomId ?: "",
+                                params.event,
+                                listOf("m.relates_to")
+                        )
+                )
             } catch (throwable: Throwable) {
                 // We said it's ok to send verification request in clear
             }
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/tasks/UploadSignaturesTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/tasks/UploadSignaturesTask.kt
index e03e353cb1..18d8b26558 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/tasks/UploadSignaturesTask.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/tasks/UploadSignaturesTask.kt
@@ -15,7 +15,6 @@
  */
 package org.matrix.android.sdk.internal.crypto.tasks
 
-import org.matrix.android.sdk.api.failure.Failure
 import org.matrix.android.sdk.internal.crypto.api.CryptoApi
 import org.matrix.android.sdk.internal.network.GlobalErrorReceiver
 import org.matrix.android.sdk.internal.network.executeRequest
@@ -34,20 +33,15 @@ internal class DefaultUploadSignaturesTask @Inject constructor(
 ) : UploadSignaturesTask {
 
     override suspend fun execute(params: UploadSignaturesTask.Params) {
-        try {
-            val response = executeRequest(
-                    globalErrorReceiver,
-                    canRetry = true,
-                    maxRetriesCount = 10
-            ) {
-                cryptoApi.uploadSignatures(params.signatures)
-            }
-            if (response.failures?.isNotEmpty() == true) {
-                throw Throwable(response.failures.toString())
-            }
-            return
-        } catch (f: Failure) {
-            throw f
+        val response = executeRequest(
+                globalErrorReceiver,
+                canRetry = true,
+                maxRetriesCount = 10
+        ) {
+            cryptoApi.uploadSignatures(params.signatures)
+        }
+        if (response.failures?.isNotEmpty() == true) {
+            throw Throwable(response.failures.toString())
         }
     }
 }
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/tasks/UploadSigningKeysTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/tasks/UploadSigningKeysTask.kt
index 0a0df11bd3..e539867a04 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/tasks/UploadSigningKeysTask.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/tasks/UploadSigningKeysTask.kt
@@ -36,7 +36,7 @@ internal interface UploadSigningKeysTask : Task PRK
+     * HkdfSha256-Extract(salt, IKM) -> PRK.
      *
      * @param salt  optional salt value (a non-secret random value);
      * if not provided, it is set to a string of HashLen (size in octets) zeros.
@@ -45,7 +45,7 @@ internal object HkdfSha256 {
     }
 
     /**
-     * HkdfSha256-Expand(PRK, info, L) -> OKM
+     * HkdfSha256-Expand(PRK, info, L) -> OKM.
      *
      * @param prk a pseudorandom key of at least HashLen bytes (usually, the output from the extract step)
      * @param info optional context and application specific information (can be empty)
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/verification/DefaultIncomingSASDefaultVerificationTransaction.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/verification/DefaultIncomingSASDefaultVerificationTransaction.kt
index 68f1cf62d5..a9d5cf1191 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/verification/DefaultIncomingSASDefaultVerificationTransaction.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/verification/DefaultIncomingSASDefaultVerificationTransaction.kt
@@ -23,8 +23,8 @@ import org.matrix.android.sdk.api.session.crypto.verification.IncomingSasVerific
 import org.matrix.android.sdk.api.session.crypto.verification.SasMode
 import org.matrix.android.sdk.api.session.crypto.verification.VerificationTxState
 import org.matrix.android.sdk.api.session.events.model.EventType
-import org.matrix.android.sdk.internal.crypto.IncomingGossipingRequestManager
-import org.matrix.android.sdk.internal.crypto.OutgoingGossipingRequestManager
+import org.matrix.android.sdk.internal.crypto.OutgoingKeyRequestManager
+import org.matrix.android.sdk.internal.crypto.SecretShareManager
 import org.matrix.android.sdk.internal.crypto.actions.SetDeviceVerificationAction
 import org.matrix.android.sdk.internal.crypto.store.IMXCryptoStore
 import timber.log.Timber
@@ -35,8 +35,8 @@ internal class DefaultIncomingSASDefaultVerificationTransaction(
         override val deviceId: String?,
         private val cryptoStore: IMXCryptoStore,
         crossSigningService: CrossSigningService,
-        outgoingGossipingRequestManager: OutgoingGossipingRequestManager,
-        incomingGossipingRequestManager: IncomingGossipingRequestManager,
+        outgoingKeyRequestManager: OutgoingKeyRequestManager,
+        secretShareManager: SecretShareManager,
         deviceFingerprint: String,
         transactionId: String,
         otherUserID: String,
@@ -47,13 +47,14 @@ internal class DefaultIncomingSASDefaultVerificationTransaction(
         deviceId,
         cryptoStore,
         crossSigningService,
-        outgoingGossipingRequestManager,
-        incomingGossipingRequestManager,
+        outgoingKeyRequestManager,
+        secretShareManager,
         deviceFingerprint,
         transactionId,
         otherUserID,
         null,
-        isIncoming = true),
+        isIncoming = true
+),
         IncomingSasVerificationTransaction {
 
     override val uxState: IncomingSasVerificationTransaction.UxState
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/verification/DefaultOutgoingSASDefaultVerificationTransaction.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/verification/DefaultOutgoingSASDefaultVerificationTransaction.kt
index e203f03b06..ddb8ed232b 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/verification/DefaultOutgoingSASDefaultVerificationTransaction.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/verification/DefaultOutgoingSASDefaultVerificationTransaction.kt
@@ -20,8 +20,8 @@ import org.matrix.android.sdk.api.session.crypto.verification.CancelCode
 import org.matrix.android.sdk.api.session.crypto.verification.OutgoingSasVerificationTransaction
 import org.matrix.android.sdk.api.session.crypto.verification.VerificationTxState
 import org.matrix.android.sdk.api.session.events.model.EventType
-import org.matrix.android.sdk.internal.crypto.IncomingGossipingRequestManager
-import org.matrix.android.sdk.internal.crypto.OutgoingGossipingRequestManager
+import org.matrix.android.sdk.internal.crypto.OutgoingKeyRequestManager
+import org.matrix.android.sdk.internal.crypto.SecretShareManager
 import org.matrix.android.sdk.internal.crypto.actions.SetDeviceVerificationAction
 import org.matrix.android.sdk.internal.crypto.store.IMXCryptoStore
 import timber.log.Timber
@@ -32,8 +32,8 @@ internal class DefaultOutgoingSASDefaultVerificationTransaction(
         deviceId: String?,
         cryptoStore: IMXCryptoStore,
         crossSigningService: CrossSigningService,
-        outgoingGossipingRequestManager: OutgoingGossipingRequestManager,
-        incomingGossipingRequestManager: IncomingGossipingRequestManager,
+        outgoingKeyRequestManager: OutgoingKeyRequestManager,
+        secretShareManager: SecretShareManager,
         deviceFingerprint: String,
         transactionId: String,
         otherUserId: String,
@@ -44,13 +44,14 @@ internal class DefaultOutgoingSASDefaultVerificationTransaction(
         deviceId,
         cryptoStore,
         crossSigningService,
-        outgoingGossipingRequestManager,
-        incomingGossipingRequestManager,
+        outgoingKeyRequestManager,
+        secretShareManager,
         deviceFingerprint,
         transactionId,
         otherUserId,
         otherDeviceId,
-        isIncoming = false),
+        isIncoming = false
+),
         OutgoingSasVerificationTransaction {
 
     override val uxState: OutgoingSasVerificationTransaction.UxState
@@ -123,7 +124,7 @@ internal class DefaultOutgoingSASDefaultVerificationTransaction(
 //        val requestMessage = KeyVerificationRequest(
 //                fromDevice = session.sessionParams.deviceId ?: "",
 //                methods = listOf(KeyVerificationStart.VERIF_METHOD_SAS),
-//                timestamp = System.currentTimeMillis().toInt(),
+//                timestamp = clock.epochMillis().toInt(),
 //                transactionId = transactionId
 //        )
 //
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/verification/DefaultVerificationService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/verification/DefaultVerificationService.kt
index 28bf1d70f7..6da674d6e4 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/verification/DefaultVerificationService.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/verification/DefaultVerificationService.kt
@@ -59,9 +59,9 @@ import org.matrix.android.sdk.api.session.room.model.message.MessageVerification
 import org.matrix.android.sdk.api.session.room.model.message.MessageVerificationStartContent
 import org.matrix.android.sdk.api.session.room.model.message.ValidVerificationDone
 import org.matrix.android.sdk.internal.crypto.DeviceListManager
-import org.matrix.android.sdk.internal.crypto.IncomingGossipingRequestManager
 import org.matrix.android.sdk.internal.crypto.MyDeviceInfoHolder
-import org.matrix.android.sdk.internal.crypto.OutgoingGossipingRequestManager
+import org.matrix.android.sdk.internal.crypto.OutgoingKeyRequestManager
+import org.matrix.android.sdk.internal.crypto.SecretShareManager
 import org.matrix.android.sdk.internal.crypto.actions.SetDeviceVerificationAction
 import org.matrix.android.sdk.internal.crypto.model.rest.KeyVerificationAccept
 import org.matrix.android.sdk.internal.crypto.model.rest.KeyVerificationCancel
@@ -84,6 +84,7 @@ import org.matrix.android.sdk.internal.di.DeviceId
 import org.matrix.android.sdk.internal.di.UserId
 import org.matrix.android.sdk.internal.session.SessionScope
 import org.matrix.android.sdk.internal.task.TaskExecutor
+import org.matrix.android.sdk.internal.util.time.Clock
 import timber.log.Timber
 import java.util.UUID
 import javax.inject.Inject
@@ -94,8 +95,8 @@ internal class DefaultVerificationService @Inject constructor(
         @UserId private val userId: String,
         @DeviceId private val deviceId: String?,
         private val cryptoStore: IMXCryptoStore,
-        private val outgoingGossipingRequestManager: OutgoingGossipingRequestManager,
-        private val incomingGossipingRequestManager: IncomingGossipingRequestManager,
+        private val outgoingKeyRequestManager: OutgoingKeyRequestManager,
+        private val secretShareManager: SecretShareManager,
         private val myDeviceInfoHolder: Lazy,
         private val deviceListManager: DeviceListManager,
         private val setDeviceVerificationAction: SetDeviceVerificationAction,
@@ -104,7 +105,8 @@ internal class DefaultVerificationService @Inject constructor(
         private val verificationTransportToDeviceFactory: VerificationTransportToDeviceFactory,
         private val crossSigningService: CrossSigningService,
         private val cryptoCoroutineScope: CoroutineScope,
-        private val taskExecutor: TaskExecutor
+        private val taskExecutor: TaskExecutor,
+        private val clock: Clock,
 ) : DefaultVerificationTransaction.Listener, VerificationService {
 
     private val uiHandler = Handler(Looper.getMainLooper())
@@ -261,9 +263,11 @@ internal class DefaultVerificationService @Inject constructor(
     }
 
     override fun markedLocallyAsManuallyVerified(userId: String, deviceID: String) {
-        setDeviceVerificationAction.handle(DeviceTrustLevel(false, true),
+        setDeviceVerificationAction.handle(
+                DeviceTrustLevel(crossSigningVerified = false, locallyVerified = true),
                 userId,
-                deviceID)
+                deviceID
+        )
 
         listeners.forEach {
             try {
@@ -313,7 +317,7 @@ internal class DefaultVerificationService @Inject constructor(
         val requestsForUser = pendingRequests.getOrPut(senderId) { mutableListOf() }
 
         val pendingVerificationRequest = PendingVerificationRequest(
-                ageLocalTs = event.ageLocalTs ?: System.currentTimeMillis(),
+                ageLocalTs = event.ageLocalTs ?: clock.epochMillis(),
                 isIncoming = true,
                 otherUserId = senderId, // requestInfo.toUserId,
                 roomId = null,
@@ -352,7 +356,7 @@ internal class DefaultVerificationService @Inject constructor(
         val requestsForUser = pendingRequests.getOrPut(senderId) { mutableListOf() }
 
         val pendingVerificationRequest = PendingVerificationRequest(
-                ageLocalTs = event.ageLocalTs ?: System.currentTimeMillis(),
+                ageLocalTs = event.ageLocalTs ?: clock.epochMillis(),
                 isIncoming = true,
                 otherUserId = senderId, // requestInfo.toUserId,
                 roomId = event.roomId,
@@ -547,12 +551,13 @@ internal class DefaultVerificationService @Inject constructor(
                             deviceId,
                             cryptoStore,
                             crossSigningService,
-                            outgoingGossipingRequestManager,
-                            incomingGossipingRequestManager,
+                            outgoingKeyRequestManager,
+                            secretShareManager,
                             myDeviceInfoHolder.get().myDevice.fingerprint()!!,
                             startReq.transactionId,
                             otherUserId,
-                            autoAccept).also { txConfigure(it) }
+                            autoAccept
+                    ).also { txConfigure(it) }
                     addTransaction(tx)
                     tx.onVerificationStart(startReq)
                     return null
@@ -644,9 +649,11 @@ internal class DefaultVerificationService @Inject constructor(
 
         if (existingRequest != null) {
             // Mark this request as cancelled
-            updatePendingRequest(existingRequest.copy(
-                    cancelConclusion = safeValueOf(cancelReq.code)
-            ))
+            updatePendingRequest(
+                    existingRequest.copy(
+                            cancelConclusion = safeValueOf(cancelReq.code)
+                    )
+            )
         }
 
         existingTransaction?.state = VerificationTxState.Cancelled(safeValueOf(cancelReq.code), false)
@@ -764,8 +771,15 @@ internal class DefaultVerificationService @Inject constructor(
             return
         }
 
+        val roomId = event.roomId
+        if (roomId == null) {
+            Timber.e("## SAS Verification missing roomId for event")
+            // TODO cancel?
+            return
+        }
+
         handleReadyReceived(event.senderId, readyReq) {
-            verificationTransportRoomMessageFactory.createTransport(event.roomId!!, it)
+            verificationTransportRoomMessageFactory.createTransport(roomId, it)
         }
     }
 
@@ -807,17 +821,15 @@ internal class DefaultVerificationService @Inject constructor(
             getExistingTransaction(userId, doneReq.transactionId)
                     ?: getOldTransaction(userId, doneReq.transactionId)
                             ?.let { vt ->
-                                val otherDeviceId = vt.otherDeviceId
+                                val otherDeviceId = vt.otherDeviceId ?: return@let
                                 if (!crossSigningService.canCrossSign()) {
-                                    outgoingGossipingRequestManager.sendSecretShareRequest(MASTER_KEY_SSSS_NAME, mapOf(userId to listOf(otherDeviceId
-                                            ?: "*")))
-                                    outgoingGossipingRequestManager.sendSecretShareRequest(SELF_SIGNING_KEY_SSSS_NAME, mapOf(userId to listOf(otherDeviceId
-                                            ?: "*")))
-                                    outgoingGossipingRequestManager.sendSecretShareRequest(USER_SIGNING_KEY_SSSS_NAME, mapOf(userId to listOf(otherDeviceId
-                                            ?: "*")))
+                                    cryptoCoroutineScope.launch {
+                                        secretShareManager.requestSecretTo(otherDeviceId, MASTER_KEY_SSSS_NAME)
+                                        secretShareManager.requestSecretTo(otherDeviceId, SELF_SIGNING_KEY_SSSS_NAME)
+                                        secretShareManager.requestSecretTo(otherDeviceId, USER_SIGNING_KEY_SSSS_NAME)
+                                        secretShareManager.requestSecretTo(otherDeviceId, KEYBACKUP_SECRET_SSSS_NAME)
+                                    }
                                 }
-                                outgoingGossipingRequestManager.sendSecretShareRequest(KEYBACKUP_SECRET_SSSS_NAME, mapOf(userId to listOf(otherDeviceId
-                                        ?: "*")))
                             }
         }
     }
@@ -911,22 +923,25 @@ internal class DefaultVerificationService @Inject constructor(
                     otherUserId = senderId,
                     otherDeviceId = readyReq.fromDevice,
                     crossSigningService = crossSigningService,
-                    outgoingGossipingRequestManager = outgoingGossipingRequestManager,
-                    incomingGossipingRequestManager = incomingGossipingRequestManager,
+                    outgoingKeyRequestManager = outgoingKeyRequestManager,
+                    secretShareManager = secretShareManager,
                     cryptoStore = cryptoStore,
                     qrCodeData = qrCodeData,
                     userId = userId,
                     deviceId = deviceId ?: "",
-                    isIncoming = false)
+                    isIncoming = false
+            )
 
             tx.transport = transportCreator.invoke(tx)
 
             addTransaction(tx)
         }
 
-        updatePendingRequest(existingRequest.copy(
-                readyInfo = readyReq
-        ))
+        updatePendingRequest(
+                existingRequest.copy(
+                        readyInfo = readyReq
+                )
+        )
     }
 
     private fun createQrCodeData(requestId: String?, otherUserId: String, otherDeviceId: String?): QrCodeData? {
@@ -1110,12 +1125,13 @@ internal class DefaultVerificationService @Inject constructor(
                     deviceId,
                     cryptoStore,
                     crossSigningService,
-                    outgoingGossipingRequestManager,
-                    incomingGossipingRequestManager,
+                    outgoingKeyRequestManager,
+                    secretShareManager,
                     myDeviceInfoHolder.get().myDevice.fingerprint()!!,
                     txID,
                     otherUserId,
-                    otherDeviceId)
+                    otherDeviceId
+            )
             tx.transport = verificationTransportToDeviceFactory.createTransport(tx)
             addTransaction(tx)
 
@@ -1150,7 +1166,7 @@ internal class DefaultVerificationService @Inject constructor(
         val validLocalId = localId ?: LocalEcho.createLocalEchoId()
 
         val verificationRequest = PendingVerificationRequest(
-                ageLocalTs = System.currentTimeMillis(),
+                ageLocalTs = clock.epochMillis(),
                 isIncoming = false,
                 roomId = roomId,
                 localId = validLocalId,
@@ -1173,16 +1189,18 @@ internal class DefaultVerificationService @Inject constructor(
         }
                 .distinct()
 
+        requestsForUser.add(verificationRequest)
         transport.sendVerificationRequest(methodValues, validLocalId, otherUserId, roomId, null) { syncedId, info ->
             // We need to update with the syncedID
-            updatePendingRequest(verificationRequest.copy(
-                    transactionId = syncedId,
-                    // localId stays different
-                    requestInfo = info
-            ))
+            updatePendingRequest(
+                    verificationRequest.copy(
+                            transactionId = syncedId,
+                            // localId stays different
+                            requestInfo = info
+                    )
+            )
         }
 
-        requestsForUser.add(verificationRequest)
         dispatchRequestAdded(verificationRequest)
 
         return verificationRequest
@@ -1228,7 +1246,7 @@ internal class DefaultVerificationService @Inject constructor(
 
         val verificationRequest = PendingVerificationRequest(
                 transactionId = localId,
-                ageLocalTs = System.currentTimeMillis(),
+                ageLocalTs = clock.epochMillis(),
                 isIncoming = false,
                 roomId = null,
                 localId = localId,
@@ -1254,10 +1272,12 @@ internal class DefaultVerificationService @Inject constructor(
 
         transport.sendVerificationRequest(methodValues, localId, otherUserId, null, targetDevices) { _, info ->
             // Nothing special to do in to device mode
-            updatePendingRequest(verificationRequest.copy(
-                    // localId stays different
-                    requestInfo = info
-            ))
+            updatePendingRequest(
+                    verificationRequest.copy(
+                            // localId stays different
+                            requestInfo = info
+                    )
+            )
         }
 
         requestsForUser.add(verificationRequest)
@@ -1271,9 +1291,11 @@ internal class DefaultVerificationService @Inject constructor(
                 .cancelTransaction(transactionId, otherUserId, null, CancelCode.User)
 
         getExistingVerificationRequest(otherUserId, transactionId)?.let {
-            updatePendingRequest(it.copy(
-                    cancelConclusion = CancelCode.User
-            ))
+            updatePendingRequest(
+                    it.copy(
+                            cancelConclusion = CancelCode.User
+                    )
+            )
         }
     }
 
@@ -1302,12 +1324,13 @@ internal class DefaultVerificationService @Inject constructor(
                     deviceId,
                     cryptoStore,
                     crossSigningService,
-                    outgoingGossipingRequestManager,
-                    incomingGossipingRequestManager,
+                    outgoingKeyRequestManager,
+                    secretShareManager,
                     myDeviceInfoHolder.get().myDevice.fingerprint()!!,
                     transactionId,
                     otherUserId,
-                    otherDeviceId)
+                    otherDeviceId
+            )
             tx.transport = verificationTransportRoomMessageFactory.createTransport(roomId, tx)
             addTransaction(tx)
 
@@ -1333,7 +1356,8 @@ internal class DefaultVerificationService @Inject constructor(
                     otherUserId,
                     existingRequest.requestInfo?.fromDevice ?: "",
                     existingRequest.requestInfo?.methods,
-                    methods) {
+                    methods
+            ) {
                 verificationTransportRoomMessageFactory.createTransport(roomId, it)
             }
             if (methods.isNullOrEmpty()) {
@@ -1343,7 +1367,8 @@ internal class DefaultVerificationService @Inject constructor(
             }
             // TODO this is not yet related to a transaction, maybe we should use another method like for cancel?
             val readyMsg = transport.createReady(transactionId, deviceId ?: "", computedMethods)
-            transport.sendToOther(EventType.KEY_VERIFICATION_READY,
+            transport.sendToOther(
+                    EventType.KEY_VERIFICATION_READY,
                     readyMsg,
                     VerificationTxState.None,
                     CancelCode.User,
@@ -1372,7 +1397,8 @@ internal class DefaultVerificationService @Inject constructor(
                     otherUserId,
                     existingRequest.requestInfo?.fromDevice ?: "",
                     existingRequest.requestInfo?.methods,
-                    methods) {
+                    methods
+            ) {
                 verificationTransportToDeviceFactory.createTransport(it)
             }
             if (methods.isNullOrEmpty()) {
@@ -1440,13 +1466,14 @@ internal class DefaultVerificationService @Inject constructor(
                         otherUserId = otherUserId,
                         otherDeviceId = otherDeviceId,
                         crossSigningService = crossSigningService,
-                        outgoingGossipingRequestManager = outgoingGossipingRequestManager,
-                        incomingGossipingRequestManager = incomingGossipingRequestManager,
+                        outgoingKeyRequestManager = outgoingKeyRequestManager,
+                        secretShareManager = secretShareManager,
                         cryptoStore = cryptoStore,
                         qrCodeData = qrCodeData,
                         userId = userId,
                         deviceId = deviceId ?: "",
-                        isIncoming = false)
+                        isIncoming = false
+                )
 
                 tx.transport = transportCreator.invoke(tx)
 
@@ -1458,7 +1485,7 @@ internal class DefaultVerificationService @Inject constructor(
     }
 
     /**
-     * This string must be unique for the pair of users performing verification for the duration that the transaction is valid
+     * This string must be unique for the pair of users performing verification for the duration that the transaction is valid.
      */
     private fun createUniqueIDForTransaction(otherUserId: String, otherDeviceID: String): String {
         return buildString {
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/verification/DefaultVerificationTransaction.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/verification/DefaultVerificationTransaction.kt
index 69d9388c5f..27bc764395 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/verification/DefaultVerificationTransaction.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/verification/DefaultVerificationTransaction.kt
@@ -20,19 +20,19 @@ import org.matrix.android.sdk.api.session.crypto.crosssigning.CrossSigningServic
 import org.matrix.android.sdk.api.session.crypto.crosssigning.DeviceTrustLevel
 import org.matrix.android.sdk.api.session.crypto.verification.VerificationTransaction
 import org.matrix.android.sdk.api.session.crypto.verification.VerificationTxState
-import org.matrix.android.sdk.internal.crypto.IncomingGossipingRequestManager
-import org.matrix.android.sdk.internal.crypto.OutgoingGossipingRequestManager
+import org.matrix.android.sdk.internal.crypto.OutgoingKeyRequestManager
+import org.matrix.android.sdk.internal.crypto.SecretShareManager
 import org.matrix.android.sdk.internal.crypto.actions.SetDeviceVerificationAction
 import timber.log.Timber
 
 /**
- * Generic interactive key verification transaction
+ * Generic interactive key verification transaction.
  */
 internal abstract class DefaultVerificationTransaction(
         private val setDeviceVerificationAction: SetDeviceVerificationAction,
         private val crossSigningService: CrossSigningService,
-        private val outgoingGossipingRequestManager: OutgoingGossipingRequestManager,
-        private val incomingGossipingRequestManager: IncomingGossipingRequestManager,
+        private val outgoingKeyRequestManager: OutgoingKeyRequestManager,
+        private val secretShareManager: SecretShareManager,
         private val userId: String,
         override val transactionId: String,
         override val otherUserId: String,
@@ -86,7 +86,7 @@ internal abstract class DefaultVerificationTransaction(
         }
 
         if (otherUserId == userId) {
-            incomingGossipingRequestManager.onVerificationCompleteForDevice(otherDeviceId!!)
+            secretShareManager.onVerificationCompleteForDevice(otherDeviceId!!)
 
             // If me it's reasonable to sign and upload the device signature
             // Notice that i might not have the private keys, so may not be able to do it
@@ -105,8 +105,10 @@ internal abstract class DefaultVerificationTransaction(
 
     private fun setDeviceVerified(userId: String, deviceId: String) {
         // TODO should not override cross sign status
-        setDeviceVerificationAction.handle(DeviceTrustLevel(crossSigningVerified = false, locallyVerified = true),
+        setDeviceVerificationAction.handle(
+                DeviceTrustLevel(crossSigningVerified = false, locallyVerified = true),
                 userId,
-                deviceId)
+                deviceId
+        )
     }
 }
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/verification/SASDefaultVerificationTransaction.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/verification/SASDefaultVerificationTransaction.kt
index 4bf01a2809..68dd57478e 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/verification/SASDefaultVerificationTransaction.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/verification/SASDefaultVerificationTransaction.kt
@@ -23,8 +23,8 @@ import org.matrix.android.sdk.api.session.crypto.verification.SasMode
 import org.matrix.android.sdk.api.session.crypto.verification.SasVerificationTransaction
 import org.matrix.android.sdk.api.session.crypto.verification.VerificationTxState
 import org.matrix.android.sdk.api.session.events.model.EventType
-import org.matrix.android.sdk.internal.crypto.IncomingGossipingRequestManager
-import org.matrix.android.sdk.internal.crypto.OutgoingGossipingRequestManager
+import org.matrix.android.sdk.internal.crypto.OutgoingKeyRequestManager
+import org.matrix.android.sdk.internal.crypto.SecretShareManager
 import org.matrix.android.sdk.internal.crypto.actions.SetDeviceVerificationAction
 import org.matrix.android.sdk.internal.crypto.store.IMXCryptoStore
 import org.matrix.android.sdk.internal.extensions.toUnsignedInt
@@ -42,8 +42,8 @@ internal abstract class SASDefaultVerificationTransaction(
         open val deviceId: String?,
         private val cryptoStore: IMXCryptoStore,
         crossSigningService: CrossSigningService,
-        outgoingGossipingRequestManager: OutgoingGossipingRequestManager,
-        incomingGossipingRequestManager: IncomingGossipingRequestManager,
+        outgoingKeyRequestManager: OutgoingKeyRequestManager,
+        secretShareManager: SecretShareManager,
         private val deviceFingerprint: String,
         transactionId: String,
         otherUserId: String,
@@ -52,13 +52,14 @@ internal abstract class SASDefaultVerificationTransaction(
 ) : DefaultVerificationTransaction(
         setDeviceVerificationAction,
         crossSigningService,
-        outgoingGossipingRequestManager,
-        incomingGossipingRequestManager,
+        outgoingKeyRequestManager,
+        secretShareManager,
         userId,
         transactionId,
         otherUserId,
         otherDeviceId,
-        isIncoming),
+        isIncoming
+),
         SasVerificationTransaction {
 
     companion object {
@@ -131,7 +132,7 @@ internal abstract class SASDefaultVerificationTransaction(
 
     /**
      * To be called by the client when the user has verified that
-     * both short codes do match
+     * both short codes do match.
      */
     override fun userHasVerifiedShortCode() {
         Timber.v("## SAS short code verified by user for id:$transactionId")
@@ -297,9 +298,11 @@ internal abstract class SASDefaultVerificationTransaction(
             return
         }
 
-        trust(otherMasterKeyIsVerified,
+        trust(
+                otherMasterKeyIsVerified,
                 verifiedDevices,
-                eventuallyMarkMyMasterKeyAsTrusted = otherMasterKey?.trustLevel?.isVerified() == false)
+                eventuallyMarkMyMasterKeyAsTrusted = otherMasterKey?.trustLevel?.isVerified() == false
+        )
     }
 
     override fun cancel() {
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/verification/SendVerificationMessageWorker.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/verification/SendVerificationMessageWorker.kt
deleted file mode 100644
index a763c05e07..0000000000
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/verification/SendVerificationMessageWorker.kt
+++ /dev/null
@@ -1,88 +0,0 @@
-/*
- * Copyright 2020 The Matrix.org Foundation C.I.C.
- *
- * 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 org.matrix.android.sdk.internal.crypto.verification
-
-import android.content.Context
-import androidx.work.Data
-import androidx.work.WorkerParameters
-import com.squareup.moshi.JsonClass
-import org.matrix.android.sdk.api.failure.shouldBeRetried
-import org.matrix.android.sdk.internal.SessionManager
-import org.matrix.android.sdk.internal.crypto.tasks.SendVerificationMessageTask
-import org.matrix.android.sdk.internal.session.SessionComponent
-import org.matrix.android.sdk.internal.session.room.send.CancelSendTracker
-import org.matrix.android.sdk.internal.session.room.send.LocalEchoRepository
-import org.matrix.android.sdk.internal.worker.SessionSafeCoroutineWorker
-import org.matrix.android.sdk.internal.worker.SessionWorkerParams
-import timber.log.Timber
-import javax.inject.Inject
-
-/**
- * Possible previous worker: None
- * Possible next worker    : None
- */
-internal class SendVerificationMessageWorker(context: Context, params: WorkerParameters, sessionManager: SessionManager) :
-    SessionSafeCoroutineWorker(context, params, sessionManager, Params::class.java) {
-
-    @JsonClass(generateAdapter = true)
-    internal data class Params(
-            override val sessionId: String,
-            val eventId: String,
-            override val lastFailureMessage: String? = null
-    ) : SessionWorkerParams
-
-    @Inject lateinit var sendVerificationMessageTask: SendVerificationMessageTask
-    @Inject lateinit var localEchoRepository: LocalEchoRepository
-    @Inject lateinit var cancelSendTracker: CancelSendTracker
-
-    override fun injectWith(injector: SessionComponent) {
-        injector.inject(this)
-    }
-
-    override suspend fun doSafeWork(params: Params): Result {
-        val localEvent = localEchoRepository.getUpToDateEcho(params.eventId) ?: return buildErrorResult(params, "Event not found")
-        val localEventId = localEvent.eventId ?: ""
-        val roomId = localEvent.roomId ?: ""
-
-        if (cancelSendTracker.isCancelRequestedFor(localEventId, roomId)) {
-            return Result.success()
-                    .also {
-                        cancelSendTracker.markCancelled(localEventId, roomId)
-                        Timber.e("## SendEvent: Event sending has been cancelled $localEventId")
-                    }
-        }
-
-        return try {
-            val resultEventId = sendVerificationMessageTask.execute(
-                    SendVerificationMessageTask.Params(
-                            event = localEvent
-                    )
-            )
-
-            Result.success(Data.Builder().putString(localEventId, resultEventId).build())
-        } catch (throwable: Throwable) {
-            if (throwable.shouldBeRetried()) {
-                Result.retry()
-            } else {
-                buildErrorResult(params, throwable.localizedMessage ?: "error")
-            }
-        }
-    }
-
-    override fun buildErrorParams(params: Params, message: String): Params {
-        return params.copy(lastFailureMessage = params.lastFailureMessage ?: message)
-    }
-}
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/verification/VerificationInfoAccept.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/verification/VerificationInfoAccept.kt
index 79aabba59d..3ddb0ca758 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/verification/VerificationInfoAccept.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/verification/VerificationInfoAccept.kt
@@ -17,28 +17,28 @@ package org.matrix.android.sdk.internal.crypto.verification
 
 internal interface VerificationInfoAccept : VerificationInfo {
     /**
-     * The key agreement protocol that Bob’s device has selected to use, out of the list proposed by Alice’s device
+     * The key agreement protocol that Bob’s device has selected to use, out of the list proposed by Alice’s device.
      */
     val keyAgreementProtocol: String?
 
     /**
-     * The hash algorithm that Bob’s device has selected to use, out of the list proposed by Alice’s device
+     * The hash algorithm that Bob’s device has selected to use, out of the list proposed by Alice’s device.
      */
     val hash: String?
 
     /**
-     * The message authentication code that Bob’s device has selected to use, out of the list proposed by Alice’s device
+     * The message authentication code that Bob’s device has selected to use, out of the list proposed by Alice’s device.
      */
     val messageAuthenticationCode: String?
 
     /**
-     * An array of short authentication string methods that Bob’s client (and Bob) understands.  Must be a subset of the list proposed by Alice’s device
+     * An array of short authentication string methods that Bob’s client (and Bob) understands.  Must be a subset of the list proposed by Alice’s device.
      */
     val shortAuthenticationStrings: List?
 
     /**
      * The hash (encoded as unpadded base64) of the concatenation of the device’s ephemeral public key (QB, encoded as unpadded base64)
-     *  and the canonical JSON representation of the m.key.verification.start message.
+     * and the canonical JSON representation of the m.key.verification.start message.
      */
     var commitment: String?
 
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/verification/VerificationInfoCancel.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/verification/VerificationInfoCancel.kt
index 35c05ac058..20e2cdcd33 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/verification/VerificationInfoCancel.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/verification/VerificationInfoCancel.kt
@@ -17,7 +17,7 @@ package org.matrix.android.sdk.internal.crypto.verification
 
 internal interface VerificationInfoCancel : VerificationInfo {
     /**
-     * machine-readable reason for cancelling, see [CancelCode]
+     * machine-readable reason for cancelling, see [CancelCode].
      */
     val code: String?
 
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/verification/VerificationInfoKey.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/verification/VerificationInfoKey.kt
index 23c117d844..2885b81a12 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/verification/VerificationInfoKey.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/verification/VerificationInfoKey.kt
@@ -20,7 +20,7 @@ package org.matrix.android.sdk.internal.crypto.verification
  */
 internal interface VerificationInfoKey : VerificationInfo {
     /**
-     * The device’s ephemeral public key, as an unpadded base64 string
+     * The device’s ephemeral public key, as an unpadded base64 string.
      */
     val key: String?
 
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/verification/VerificationInfoMac.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/verification/VerificationInfoMac.kt
index 5515acc2f1..d6f1d7e4db 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/verification/VerificationInfoMac.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/verification/VerificationInfoMac.kt
@@ -17,7 +17,7 @@ package org.matrix.android.sdk.internal.crypto.verification
 
 internal interface VerificationInfoMac : VerificationInfo {
     /**
-     * A map of key ID to the MAC of the key, as an unpadded base64 string, calculated using the MAC key
+     * A map of key ID to the MAC of the key, as an unpadded base64 string, calculated using the MAC key.
      */
     val mac: Map?
 
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/verification/VerificationInfoReady.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/verification/VerificationInfoReady.kt
index 327c09dabf..2e397eee08 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/verification/VerificationInfoReady.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/verification/VerificationInfoReady.kt
@@ -27,12 +27,12 @@ import org.matrix.android.sdk.api.session.crypto.verification.ValidVerificationI
 
 internal interface VerificationInfoReady : VerificationInfo {
     /**
-     * The ID of the device that sent the m.key.verification.ready message
+     * The ID of the device that sent the m.key.verification.ready message.
      */
     val fromDevice: String?
 
     /**
-     * An array of verification methods that the device supports
+     * An array of verification methods that the device supports.
      */
     val methods: List?
 
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/verification/VerificationInfoStart.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/verification/VerificationInfoStart.kt
index 40c96dfa95..d71bd7359e 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/verification/VerificationInfoStart.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/verification/VerificationInfoStart.kt
@@ -24,7 +24,7 @@ internal interface VerificationInfoStart : VerificationInfo?
 
     /**
-     * Shared secret, when starting verification with QR code
+     * Shared secret, when starting verification with QR code.
      */
     val sharedSecret: String?
 
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/verification/VerificationMessageProcessor.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/verification/VerificationMessageProcessor.kt
index 52166761ab..303b6c5839 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/verification/VerificationMessageProcessor.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/verification/VerificationMessageProcessor.kt
@@ -15,13 +15,9 @@
  */
 package org.matrix.android.sdk.internal.crypto.verification
 
-import io.realm.Realm
-import org.matrix.android.sdk.api.session.crypto.MXCryptoError
-import org.matrix.android.sdk.api.session.crypto.model.OlmDecryptionResult
 import org.matrix.android.sdk.api.session.crypto.verification.VerificationService
 import org.matrix.android.sdk.api.session.events.model.Event
 import org.matrix.android.sdk.api.session.events.model.EventType
-import org.matrix.android.sdk.api.session.events.model.LocalEcho
 import org.matrix.android.sdk.api.session.events.model.toModel
 import org.matrix.android.sdk.api.session.room.model.message.MessageContent
 import org.matrix.android.sdk.api.session.room.model.message.MessageRelationContent
@@ -29,20 +25,18 @@ import org.matrix.android.sdk.api.session.room.model.message.MessageType
 import org.matrix.android.sdk.api.session.room.model.message.MessageVerificationReadyContent
 import org.matrix.android.sdk.api.session.room.model.message.MessageVerificationRequestContent
 import org.matrix.android.sdk.api.session.room.model.message.MessageVerificationStartContent
-import org.matrix.android.sdk.internal.crypto.EventDecryptor
-import org.matrix.android.sdk.internal.database.model.EventInsertType
 import org.matrix.android.sdk.internal.di.DeviceId
 import org.matrix.android.sdk.internal.di.UserId
-import org.matrix.android.sdk.internal.session.EventInsertLiveProcessor
+import org.matrix.android.sdk.internal.util.time.Clock
 import timber.log.Timber
 import javax.inject.Inject
 
 internal class VerificationMessageProcessor @Inject constructor(
-        private val eventDecryptor: EventDecryptor,
         private val verificationService: DefaultVerificationService,
         @UserId private val userId: String,
-        @DeviceId private val deviceId: String?
-) : EventInsertLiveProcessor {
+        @DeviceId private val deviceId: String?,
+        private val clock: Clock,
+) {
 
     private val transactionsHandledByOtherDevice = ArrayList()
 
@@ -58,41 +52,20 @@ internal class VerificationMessageProcessor @Inject constructor(
             EventType.ENCRYPTED
     )
 
-    override fun shouldProcess(eventId: String, eventType: String, insertType: EventInsertType): Boolean {
-        if (insertType != EventInsertType.INCREMENTAL_SYNC) {
-            return false
-        }
-        return allowedTypes.contains(eventType) && !LocalEcho.isLocalEchoId(eventId)
+    fun shouldProcess(eventType: String): Boolean {
+        return allowedTypes.contains(eventType)
     }
 
-    override suspend fun process(realm: Realm, event: Event) {
-        Timber.v("## SAS Verification live observer: received msgId: ${event.eventId} msgtype: ${event.type} from ${event.senderId}")
+    suspend fun process(event: Event) {
+        Timber.v("## SAS Verification live observer: received msgId: ${event.eventId} msgtype: ${event.getClearType()} from ${event.senderId}")
 
         // If the request is in the future by more than 5 minutes or more than 10 minutes in the past,
         // the message should be ignored by the receiver.
 
-        if (!VerificationService.isValidRequest(event.ageLocalTs
-                        ?: event.originServerTs)) return Unit.also {
-            Timber.d("## SAS Verification live observer: msgId: ${event.eventId} is outdated")
+        if (event.ageLocalTs != null && !VerificationService.isValidRequest(event.ageLocalTs, clock.epochMillis())) return Unit.also {
+            Timber.d("## SAS Verification live observer: msgId: ${event.eventId} is outdated age:$event.ageLocalTs ms")
         }
 
-        // decrypt if needed?
-        if (event.isEncrypted() && event.mxDecryptionResult == null) {
-            // TODO use a global event decryptor? attache to session and that listen to new sessionId?
-            // for now decrypt sync
-            try {
-                val result = eventDecryptor.decryptEvent(event, "")
-                event.mxDecryptionResult = OlmDecryptionResult(
-                        payload = result.clearEvent,
-                        senderKey = result.senderCurve25519Key,
-                        keysClaimed = result.claimedEd25519Key?.let { mapOf("ed25519" to it) },
-                        forwardingCurve25519KeyChain = result.forwardingCurve25519KeyChain
-                )
-            } catch (e: MXCryptoError) {
-                Timber.e("## SAS Failed to decrypt event: ${event.eventId}")
-                verificationService.onPotentiallyInterestingEventRoomFailToDecrypt(event)
-            }
-        }
         Timber.v("## SAS Verification live observer: received msgId: ${event.eventId} type: ${event.getClearType()}")
 
         // Relates to is not encrypted
@@ -101,7 +74,6 @@ internal class VerificationMessageProcessor @Inject constructor(
         if (event.senderId == userId) {
             // If it's send from me, we need to keep track of Requests or Start
             // done from another device of mine
-
             if (EventType.MESSAGE == event.getClearType()) {
                 val msgType = event.getClearContent().toModel()?.msgType
                 if (MessageType.MSGTYPE_VERIFICATION_REQUEST == msgType) {
@@ -136,6 +108,8 @@ internal class VerificationMessageProcessor @Inject constructor(
                     transactionsHandledByOtherDevice.remove(it)
                     verificationService.onRoomRequestHandledByOtherDevice(event)
                 }
+            } else if (EventType.ENCRYPTED == event.getClearType()) {
+                verificationService.onPotentiallyInterestingEventRoomFailToDecrypt(event)
             }
 
             Timber.v("## SAS Verification ignoring message sent by me: ${event.eventId} type: ${event.getClearType()}")
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/verification/VerificationTransport.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/verification/VerificationTransport.kt
index c326dd0fc3..c12aea9d52 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/verification/VerificationTransport.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/verification/VerificationTransport.kt
@@ -26,7 +26,7 @@ import org.matrix.android.sdk.api.session.crypto.verification.VerificationTxStat
 internal interface VerificationTransport {
 
     /**
-     * Sends a message
+     * Sends a message.
      */
     fun  sendToOther(type: String,
                         verificationInfo: VerificationInfo,
@@ -53,7 +53,7 @@ internal interface VerificationTransport {
              onDone: (() -> Unit)?)
 
     /**
-     * Creates an accept message suitable for this transport
+     * Creates an accept message suitable for this transport.
      */
     fun createAccept(tid: String,
                      keyAgreementProtocol: String,
@@ -66,7 +66,7 @@ internal interface VerificationTransport {
                   pubKey: String): VerificationInfoKey
 
     /**
-     * Create start for SAS verification
+     * Create start for SAS verification.
      */
     fun createStartForSas(fromDevice: String,
                           transactionId: String,
@@ -76,7 +76,7 @@ internal interface VerificationTransport {
                           shortAuthenticationStrings: List): VerificationInfoStart
 
     /**
-     * Create start for QR code verification
+     * Create start for QR code verification.
      */
     fun createStartForQrCode(fromDevice: String,
                              transactionId: String,
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/verification/VerificationTransportRoomMessage.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/verification/VerificationTransportRoomMessage.kt
index bd1186908c..e32828af23 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/verification/VerificationTransportRoomMessage.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/verification/VerificationTransportRoomMessage.kt
@@ -15,14 +15,8 @@
  */
 package org.matrix.android.sdk.internal.crypto.verification
 
-import androidx.lifecycle.Observer
-import androidx.work.BackoffPolicy
-import androidx.work.Data
-import androidx.work.ExistingWorkPolicy
-import androidx.work.Operation
-import androidx.work.WorkInfo
 import kotlinx.coroutines.CoroutineScope
-import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.asCoroutineDispatcher
 import kotlinx.coroutines.launch
 import org.matrix.android.sdk.api.session.crypto.verification.CancelCode
 import org.matrix.android.sdk.api.session.crypto.verification.ValidVerificationInfoRequest
@@ -45,25 +39,28 @@ import org.matrix.android.sdk.api.session.room.model.message.MessageVerification
 import org.matrix.android.sdk.api.session.room.model.relation.RelationDefaultContent
 import org.matrix.android.sdk.internal.crypto.model.rest.VERIFICATION_METHOD_RECIPROCATE
 import org.matrix.android.sdk.internal.crypto.model.rest.VERIFICATION_METHOD_SAS
-import org.matrix.android.sdk.internal.di.WorkManagerProvider
+import org.matrix.android.sdk.internal.crypto.tasks.SendVerificationMessageTask
 import org.matrix.android.sdk.internal.session.room.send.LocalEchoEventFactory
-import org.matrix.android.sdk.internal.worker.SessionSafeCoroutineWorker
-import org.matrix.android.sdk.internal.worker.WorkerParamsFactory
+import org.matrix.android.sdk.internal.task.SemaphoreCoroutineSequencer
+import org.matrix.android.sdk.internal.util.time.Clock
 import timber.log.Timber
-import java.util.UUID
-import java.util.concurrent.TimeUnit
+import java.util.concurrent.Executors
 
 internal class VerificationTransportRoomMessage(
-        private val workManagerProvider: WorkManagerProvider,
-        private val sessionId: String,
+        private val sendVerificationMessageTask: SendVerificationMessageTask,
         private val userId: String,
         private val userDeviceId: String?,
         private val roomId: String,
         private val localEchoEventFactory: LocalEchoEventFactory,
         private val tx: DefaultVerificationTransaction?,
-        private val coroutineScope: CoroutineScope
+        cryptoCoroutineScope: CoroutineScope,
+        private val clock: Clock,
 ) : VerificationTransport {
 
+    private val dispatcher = Executors.newSingleThreadExecutor().asCoroutineDispatcher()
+    private val verificationSenderScope = CoroutineScope(cryptoCoroutineScope.coroutineContext + dispatcher)
+    private val sequencer = SemaphoreCoroutineSequencer()
+
     override fun  sendToOther(type: String,
                                  verificationInfo: VerificationInfo,
                                  nextState: VerificationTxState,
@@ -77,68 +74,22 @@ internal class VerificationTransportRoomMessage(
                 content = verificationInfo.toEventContent()!!
         )
 
-        val workerParams = WorkerParamsFactory.toData(SendVerificationMessageWorker.Params(
-                sessionId = sessionId,
-                eventId = event.eventId ?: ""
-        ))
-        val enqueueInfo = enqueueSendWork(workerParams)
-
-        // I cannot just listen to the given work request, because when used in a uniqueWork,
-        // The callback is called while it is still Running ...
-
-//        Futures.addCallback(enqueueInfo.first.result, object : FutureCallback {
-//            override fun onSuccess(result: Operation.State.SUCCESS?) {
-//                if (onDone != null) {
-//                    onDone()
-//                } else {
-//                    tx?.state = nextState
-//                }
-//            }
-//
-//            override fun onFailure(t: Throwable) {
-//                Timber.e("## SAS verification [${tx?.transactionId}] failed to send toDevice in state : ${tx?.state}, reason: ${t.localizedMessage}")
-//                tx?.cancel(onErrorReason)
-//            }
-//        }, listenerExecutor)
-
-        val workLiveData = workManagerProvider.workManager
-                .getWorkInfosForUniqueWorkLiveData(uniqueQueueName())
-
-        val observer = object : Observer> {
-            override fun onChanged(workInfoList: List?) {
-                workInfoList
-                        ?.firstOrNull { it.id == enqueueInfo.second }
-                        ?.let { wInfo ->
-                            when (wInfo.state) {
-                                WorkInfo.State.FAILED    -> {
-                                    tx?.cancel(onErrorReason)
-                                    workLiveData.removeObserver(this)
-                                }
-                                WorkInfo.State.SUCCEEDED -> {
-                                    if (SessionSafeCoroutineWorker.hasFailed(wInfo.outputData)) {
-                                        Timber.e("## SAS verification [${tx?.transactionId}] failed to send verification message in state : ${tx?.state}")
-                                        tx?.cancel(onErrorReason)
-                                    } else {
-                                        if (onDone != null) {
-                                            onDone()
-                                        } else {
-                                            tx?.state = nextState
-                                        }
-                                    }
-                                    workLiveData.removeObserver(this)
-                                }
-                                else                     -> {
-                                    // nop
-                                }
-                            }
-                        }
+        verificationSenderScope.launch {
+            sequencer.post {
+                try {
+                    val params = SendVerificationMessageTask.Params(event)
+                    sendVerificationMessageTask.executeRetry(params, 5)
+                    // Do I need to update local echo state to sent?
+                    if (onDone != null) {
+                        onDone()
+                    } else {
+                        tx?.state = nextState
+                    }
+                } catch (failure: Throwable) {
+                    tx?.cancel(onErrorReason)
+                }
             }
         }
-
-        // TODO listen to DB to get synced info
-        coroutineScope.launch(Dispatchers.Main) {
-            workLiveData.observeForever(observer)
-        }
     }
 
     override fun sendVerificationRequest(supportedMethods: List,
@@ -155,7 +106,7 @@ internal class VerificationTransportRoomMessage(
                 transactionId = "",
                 fromDevice = userDeviceId ?: "",
                 methods = supportedMethods,
-                timestamp = System.currentTimeMillis()
+                timestamp = clock.epochMillis()
         )
 
         val info = MessageVerificationRequestContent(
@@ -169,58 +120,24 @@ internal class VerificationTransportRoomMessage(
         val content = info.toContent()
 
         val event = createEventAndLocalEcho(
-                localId,
-                EventType.MESSAGE,
-                roomId,
-                content
+                localId = localId,
+                type = EventType.MESSAGE,
+                roomId = roomId,
+                content = content
         )
 
-        val workerParams = WorkerParamsFactory.toData(SendVerificationMessageWorker.Params(
-                sessionId = sessionId,
-                eventId = event.eventId ?: ""
-        ))
-
-        val workRequest = workManagerProvider.matrixOneTimeWorkRequestBuilder()
-                .setConstraints(WorkManagerProvider.workConstraints)
-                .setInputData(workerParams)
-                .setBackoffCriteria(BackoffPolicy.LINEAR, WorkManagerProvider.BACKOFF_DELAY_MILLIS, TimeUnit.MILLISECONDS)
-                .build()
-
-        workManagerProvider.workManager
-                .beginUniqueWork("${roomId}_VerificationWork", ExistingWorkPolicy.APPEND_OR_REPLACE, workRequest)
-                .enqueue()
-
-        // I cannot just listen to the given work request, because when used in a uniqueWork,
-        // The callback is called while it is still Running ...
-
-        val workLiveData = workManagerProvider.workManager
-                .getWorkInfosForUniqueWorkLiveData("${roomId}_VerificationWork")
-
-        val observer = object : Observer> {
-            override fun onChanged(workInfoList: List?) {
-                workInfoList
-                        ?.filter { it.state == WorkInfo.State.SUCCEEDED }
-                        ?.firstOrNull { it.id == workRequest.id }
-                        ?.let { wInfo ->
-                            if (SessionSafeCoroutineWorker.hasFailed(wInfo.outputData)) {
-                                callback(null, null)
-                            } else {
-                                val eventId = wInfo.outputData.getString(localId)
-                                if (eventId != null) {
-                                    callback(eventId, validInfo)
-                                } else {
-                                    callback(null, null)
-                                }
-                            }
-                            workLiveData.removeObserver(this)
-                        }
+        verificationSenderScope.launch {
+            val params = SendVerificationMessageTask.Params(event)
+            sequencer.post {
+                try {
+                    val eventId = sendVerificationMessageTask.executeRetry(params, 5)
+                    // Do I need to update local echo state to sent?
+                    callback(eventId, validInfo)
+                } catch (failure: Throwable) {
+                    callback(null, null)
+                }
             }
         }
-
-        // TODO listen to DB to get synced info
-        coroutineScope.launch(Dispatchers.Main) {
-            workLiveData.observeForever(observer)
-        }
     }
 
     override fun cancelTransaction(transactionId: String, otherUserId: String, otherUserDeviceId: String?, code: CancelCode) {
@@ -230,11 +147,17 @@ internal class VerificationTransportRoomMessage(
                 roomId = roomId,
                 content = MessageVerificationCancelContent.create(transactionId, code).toContent()
         )
-        val workerParams = WorkerParamsFactory.toData(SendVerificationMessageWorker.Params(
-                sessionId = sessionId,
-                eventId = event.eventId ?: ""
-        ))
-        enqueueSendWork(workerParams)
+
+        verificationSenderScope.launch {
+            sequencer.post {
+                try {
+                    val params = SendVerificationMessageTask.Params(event)
+                    sendVerificationMessageTask.executeRetry(params, 5)
+                } catch (failure: Throwable) {
+                    Timber.w(failure, "Failed to cancel verification transaction")
+                }
+            }
+        }
     }
 
     override fun done(transactionId: String,
@@ -250,45 +173,21 @@ internal class VerificationTransportRoomMessage(
                         )
                 ).toContent()
         )
-        val workerParams = WorkerParamsFactory.toData(SendVerificationMessageWorker.Params(
-                sessionId = sessionId,
-                eventId = event.eventId ?: ""
-        ))
-        val enqueueInfo = enqueueSendWork(workerParams)
-
-        val workLiveData = workManagerProvider.workManager
-                .getWorkInfosForUniqueWorkLiveData(uniqueQueueName())
-        val observer = object : Observer> {
-            override fun onChanged(workInfoList: List?) {
-                workInfoList
-                        ?.filter { it.state == WorkInfo.State.SUCCEEDED }
-                        ?.firstOrNull { it.id == enqueueInfo.second }
-                        ?.let { _ ->
-                            onDone?.invoke()
-                            workLiveData.removeObserver(this)
-                        }
+        verificationSenderScope.launch {
+            sequencer.post {
+                try {
+                    val params = SendVerificationMessageTask.Params(event)
+                    sendVerificationMessageTask.executeRetry(params, 5)
+                } catch (failure: Throwable) {
+                    Timber.w(failure, "Failed to complete (done) verification")
+                    // should we call onDone?
+                } finally {
+                    onDone?.invoke()
+                }
             }
         }
-
-        // TODO listen to DB to get synced info
-        coroutineScope.launch(Dispatchers.Main) {
-            workLiveData.observeForever(observer)
-        }
     }
 
-    private fun enqueueSendWork(workerParams: Data): Pair {
-        val workRequest = workManagerProvider.matrixOneTimeWorkRequestBuilder()
-                .setConstraints(WorkManagerProvider.workConstraints)
-                .setInputData(workerParams)
-                .setBackoffCriteria(BackoffPolicy.LINEAR, WorkManagerProvider.BACKOFF_DELAY_MILLIS, TimeUnit.MILLISECONDS)
-                .build()
-        return workManagerProvider.workManager
-                .beginUniqueWork(uniqueQueueName(), ExistingWorkPolicy.APPEND_OR_REPLACE, workRequest)
-                .enqueue() to workRequest.id
-    }
-
-    private fun uniqueQueueName() = "${roomId}_VerificationWork"
-
     override fun createAccept(tid: String,
                               keyAgreementProtocol: String,
                               hash: String,
@@ -296,13 +195,13 @@ internal class VerificationTransportRoomMessage(
                               messageAuthenticationCode: String,
                               shortAuthenticationStrings: List): VerificationInfoAccept =
             MessageVerificationAcceptContent.create(
-            tid,
-            keyAgreementProtocol,
-            hash,
-            commitment,
-            messageAuthenticationCode,
-            shortAuthenticationStrings
-    )
+                    tid,
+                    keyAgreementProtocol,
+                    hash,
+                    commitment,
+                    messageAuthenticationCode,
+                    shortAuthenticationStrings
+            )
 
     override fun createKey(tid: String, pubKey: String): VerificationInfoKey = MessageVerificationKeyContent.create(tid, pubKey)
 
@@ -361,7 +260,7 @@ internal class VerificationTransportRoomMessage(
     private fun createEventAndLocalEcho(localId: String = LocalEcho.createLocalEchoId(), type: String, roomId: String, content: Content): Event {
         return Event(
                 roomId = roomId,
-                originServerTs = System.currentTimeMillis(),
+                originServerTs = clock.epochMillis(),
                 senderId = userId,
                 eventId = localId,
                 type = type,
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/verification/VerificationTransportRoomMessageFactory.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/verification/VerificationTransportRoomMessageFactory.kt
index f89127273b..345948e608 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/verification/VerificationTransportRoomMessageFactory.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/verification/VerificationTransportRoomMessageFactory.kt
@@ -16,34 +16,35 @@
 
 package org.matrix.android.sdk.internal.crypto.verification
 
+import kotlinx.coroutines.CoroutineScope
+import org.matrix.android.sdk.internal.crypto.tasks.SendVerificationMessageTask
 import org.matrix.android.sdk.internal.di.DeviceId
-import org.matrix.android.sdk.internal.di.SessionId
 import org.matrix.android.sdk.internal.di.UserId
-import org.matrix.android.sdk.internal.di.WorkManagerProvider
 import org.matrix.android.sdk.internal.session.room.send.LocalEchoEventFactory
-import org.matrix.android.sdk.internal.task.TaskExecutor
+import org.matrix.android.sdk.internal.util.time.Clock
 import javax.inject.Inject
 
 internal class VerificationTransportRoomMessageFactory @Inject constructor(
-        private val workManagerProvider: WorkManagerProvider,
-        @SessionId
-        private val sessionId: String,
+        private val sendVerificationMessageTask: SendVerificationMessageTask,
         @UserId
         private val userId: String,
         @DeviceId
         private val deviceId: String?,
         private val localEchoEventFactory: LocalEchoEventFactory,
-        private val taskExecutor: TaskExecutor
+        private val cryptoCoroutineScope: CoroutineScope,
+        private val clock: Clock,
 ) {
 
     fun createTransport(roomId: String, tx: DefaultVerificationTransaction?): VerificationTransportRoomMessage {
-        return VerificationTransportRoomMessage(workManagerProvider,
-                sessionId,
-                userId,
-                deviceId,
-                roomId,
-                localEchoEventFactory,
-                tx,
-                taskExecutor.executorScope)
+        return VerificationTransportRoomMessage(
+                sendVerificationMessageTask = sendVerificationMessageTask,
+                userId = userId,
+                userDeviceId = deviceId,
+                roomId = roomId,
+                localEchoEventFactory = localEchoEventFactory,
+                tx = tx,
+                cryptoCoroutineScope = cryptoCoroutineScope,
+                clock = clock,
+        )
     }
 }
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/verification/VerificationTransportToDevice.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/verification/VerificationTransportToDevice.kt
index 40deda2745..bc24ef2966 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/verification/VerificationTransportToDevice.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/verification/VerificationTransportToDevice.kt
@@ -35,13 +35,16 @@ import org.matrix.android.sdk.internal.crypto.model.rest.VERIFICATION_METHOD_SAS
 import org.matrix.android.sdk.internal.crypto.tasks.SendToDeviceTask
 import org.matrix.android.sdk.internal.task.TaskExecutor
 import org.matrix.android.sdk.internal.task.configureWith
+import org.matrix.android.sdk.internal.util.time.Clock
 import timber.log.Timber
 
+// TODO var could be val
 internal class VerificationTransportToDevice(
         private var tx: DefaultVerificationTransaction?,
         private var sendToDeviceTask: SendToDeviceTask,
         private val myDeviceId: String?,
-        private var taskExecutor: TaskExecutor
+        private var taskExecutor: TaskExecutor,
+        private val clock: Clock,
 ) : VerificationTransport {
 
     override fun sendVerificationRequest(supportedMethods: List,
@@ -56,7 +59,7 @@ internal class VerificationTransportToDevice(
                 transactionId = localId,
                 fromDevice = myDeviceId ?: "",
                 methods = supportedMethods,
-                timestamp = System.currentTimeMillis()
+                timestamp = clock.epochMillis()
         )
         val keyReq = KeyVerificationRequest(
                 fromDevice = validKeyReq.fromDevice,
@@ -201,7 +204,8 @@ internal class VerificationTransportToDevice(
             hash,
             commitment,
             messageAuthenticationCode,
-            shortAuthenticationStrings)
+            shortAuthenticationStrings
+    )
 
     override fun createKey(tid: String, pubKey: String): VerificationInfoKey = KeyVerificationKey.create(tid, pubKey)
 
@@ -221,7 +225,8 @@ internal class VerificationTransportToDevice(
                 hashes,
                 messageAuthenticationCodes,
                 shortAuthenticationStrings,
-                null)
+                null
+        )
     }
 
     override fun createStartForQrCode(fromDevice: String,
@@ -235,7 +240,8 @@ internal class VerificationTransportToDevice(
                 null,
                 null,
                 null,
-                sharedSecret)
+                sharedSecret
+        )
     }
 
     override fun createReady(tid: String, fromDevice: String, methods: List): VerificationInfoReady {
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/verification/VerificationTransportToDeviceFactory.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/verification/VerificationTransportToDeviceFactory.kt
index e9a2c65ef7..312d911822 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/verification/VerificationTransportToDeviceFactory.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/verification/VerificationTransportToDeviceFactory.kt
@@ -19,14 +19,17 @@ package org.matrix.android.sdk.internal.crypto.verification
 import org.matrix.android.sdk.internal.crypto.tasks.SendToDeviceTask
 import org.matrix.android.sdk.internal.di.DeviceId
 import org.matrix.android.sdk.internal.task.TaskExecutor
+import org.matrix.android.sdk.internal.util.time.Clock
 import javax.inject.Inject
 
 internal class VerificationTransportToDeviceFactory @Inject constructor(
         private val sendToDeviceTask: SendToDeviceTask,
         @DeviceId val myDeviceId: String?,
-        private val taskExecutor: TaskExecutor) {
+        private val taskExecutor: TaskExecutor,
+        private val clock: Clock,
+) {
 
     fun createTransport(tx: DefaultVerificationTransaction?): VerificationTransportToDevice {
-        return VerificationTransportToDevice(tx, sendToDeviceTask, myDeviceId, taskExecutor)
+        return VerificationTransportToDevice(tx, sendToDeviceTask, myDeviceId, taskExecutor, clock)
     }
 }
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/verification/qrcode/DefaultQrCodeVerificationTransaction.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/verification/qrcode/DefaultQrCodeVerificationTransaction.kt
index 06f0b36798..b1ad4d87b6 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/verification/qrcode/DefaultQrCodeVerificationTransaction.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/verification/qrcode/DefaultQrCodeVerificationTransaction.kt
@@ -22,8 +22,8 @@ import org.matrix.android.sdk.api.session.crypto.verification.QrCodeVerification
 import org.matrix.android.sdk.api.session.crypto.verification.VerificationTxState
 import org.matrix.android.sdk.api.session.events.model.EventType
 import org.matrix.android.sdk.api.util.fromBase64
-import org.matrix.android.sdk.internal.crypto.IncomingGossipingRequestManager
-import org.matrix.android.sdk.internal.crypto.OutgoingGossipingRequestManager
+import org.matrix.android.sdk.internal.crypto.OutgoingKeyRequestManager
+import org.matrix.android.sdk.internal.crypto.SecretShareManager
 import org.matrix.android.sdk.internal.crypto.actions.SetDeviceVerificationAction
 import org.matrix.android.sdk.internal.crypto.crosssigning.fromBase64Safe
 import org.matrix.android.sdk.internal.crypto.store.IMXCryptoStore
@@ -37,8 +37,8 @@ internal class DefaultQrCodeVerificationTransaction(
         override val otherUserId: String,
         override var otherDeviceId: String?,
         private val crossSigningService: CrossSigningService,
-        outgoingGossipingRequestManager: OutgoingGossipingRequestManager,
-        incomingGossipingRequestManager: IncomingGossipingRequestManager,
+        outgoingKeyRequestManager: OutgoingKeyRequestManager,
+        secretShareManager: SecretShareManager,
         private val cryptoStore: IMXCryptoStore,
         // Not null only if other user is able to scan QR code
         private val qrCodeData: QrCodeData?,
@@ -48,13 +48,14 @@ internal class DefaultQrCodeVerificationTransaction(
 ) : DefaultVerificationTransaction(
         setDeviceVerificationAction,
         crossSigningService,
-        outgoingGossipingRequestManager,
-        incomingGossipingRequestManager,
+        outgoingKeyRequestManager,
+        secretShareManager,
         userId,
         transactionId,
         otherUserId,
         otherDeviceId,
-        isIncoming),
+        isIncoming
+),
         QrCodeVerificationTransaction {
 
     override val qrCodeText: String?
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/verification/qrcode/QrCodeData.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/verification/qrcode/QrCodeData.kt
index 0ac57db9bc..f308807e04 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/verification/qrcode/QrCodeData.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/verification/qrcode/QrCodeData.kt
@@ -21,34 +21,34 @@ package org.matrix.android.sdk.internal.crypto.verification.qrcode
  */
 internal sealed class QrCodeData(
         /**
-         * the event ID or transaction_id of the associated verification
+         * the event ID or transaction_id of the associated verification.
          */
         open val transactionId: String,
         /**
-         * First key (32 bytes, in base64 no padding)
+         * First key (32 bytes, in base64 no padding).
          */
         val firstKey: String,
         /**
-         * Second key (32 bytes, in base64 no padding)
+         * Second key (32 bytes, in base64 no padding).
          */
         val secondKey: String,
         /**
-         * a random shared secret (in base64 no padding)
+         * a random shared secret (in base64 no padding).
          */
         open val sharedSecret: String
 ) {
     /**
-     * verifying another user with cross-signing
-     * QR code verification mode: 0x00
+     * Verifying another user with cross-signing
+     * QR code verification mode: 0x00.
      */
     data class VerifyingAnotherUser(
             override val transactionId: String,
             /**
-             * the user's own master cross-signing public key
+             * the user's own master cross-signing public key.
              */
             val userMasterCrossSigningPublicKey: String,
             /**
-             * what the device thinks the other user's master cross-signing key is
+             * what the device thinks the other user's master cross-signing key is.
              */
             val otherUserMasterCrossSigningPublicKey: String,
             override val sharedSecret: String
@@ -56,20 +56,21 @@ internal sealed class QrCodeData(
             transactionId,
             userMasterCrossSigningPublicKey,
             otherUserMasterCrossSigningPublicKey,
-            sharedSecret)
+            sharedSecret
+    )
 
     /**
      * self-verifying in which the current device does trust the master key
-     * QR code verification mode: 0x01
+     * QR code verification mode: 0x01.
      */
     data class SelfVerifyingMasterKeyTrusted(
             override val transactionId: String,
             /**
-             * the user's own master cross-signing public key
+             * the user's own master cross-signing public key.
              */
             val userMasterCrossSigningPublicKey: String,
             /**
-             * what the device thinks the other device's device key is
+             * what the device thinks the other device's device key is.
              */
             val otherDeviceKey: String,
             override val sharedSecret: String
@@ -77,20 +78,21 @@ internal sealed class QrCodeData(
             transactionId,
             userMasterCrossSigningPublicKey,
             otherDeviceKey,
-            sharedSecret)
+            sharedSecret
+    )
 
     /**
      * self-verifying in which the current device does not yet trust the master key
-     * QR code verification mode: 0x02
+     * QR code verification mode: 0x02.
      */
     data class SelfVerifyingMasterKeyNotTrusted(
             override val transactionId: String,
             /**
-             * the current device's device key
+             * the current device's device key.
              */
             val deviceKey: String,
             /**
-             * what the device thinks the user's master cross-signing key is
+             * what the device thinks the user's master cross-signing key is.
              */
             val userMasterCrossSigningPublicKey: String,
             override val sharedSecret: String
@@ -98,5 +100,6 @@ internal sealed class QrCodeData(
             transactionId,
             deviceKey,
             userMasterCrossSigningPublicKey,
-            sharedSecret)
+            sharedSecret
+    )
 }
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/AsyncTransaction.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/AsyncTransaction.kt
index 315d77d932..7d263f1937 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/AsyncTransaction.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/AsyncTransaction.kt
@@ -24,6 +24,7 @@ import kotlinx.coroutines.isActive
 import kotlinx.coroutines.launch
 import kotlinx.coroutines.withContext
 import timber.log.Timber
+import kotlin.system.measureTimeMillis
 
 internal fun  CoroutineScope.asyncTransaction(monarchy: Monarchy, transaction: suspend (realm: Realm) -> T) {
     asyncTransaction(monarchy.realmConfiguration, transaction)
@@ -41,13 +42,13 @@ internal suspend fun  awaitTransaction(config: RealmConfiguration, transactio
             bgRealm.beginTransaction()
             val result: T
             try {
-                val start = System.currentTimeMillis()
-                result = transaction(bgRealm)
-                if (isActive) {
-                    bgRealm.commitTransaction()
-                    val end = System.currentTimeMillis()
-                    val time = end - start
-                    Timber.v("Execute transaction in $time millis")
+                measureTimeMillis {
+                    result = transaction(bgRealm)
+                    if (isActive) {
+                        bgRealm.commitTransaction()
+                    }
+                }.also {
+                    Timber.v("Execute transaction in $it millis")
                 }
             } finally {
                 if (bgRealm.isInTransaction) {
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/EventInsertLiveObserver.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/EventInsertLiveObserver.kt
index 115025cc7d..751992fa7f 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/EventInsertLiveObserver.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/EventInsertLiveObserver.kt
@@ -19,6 +19,9 @@ package org.matrix.android.sdk.internal.database
 import com.zhuinden.monarchy.Monarchy
 import io.realm.RealmConfiguration
 import io.realm.RealmResults
+import kotlinx.coroutines.flow.MutableSharedFlow
+import kotlinx.coroutines.flow.launchIn
+import kotlinx.coroutines.flow.onEach
 import kotlinx.coroutines.launch
 import org.matrix.android.sdk.internal.database.mapper.asDomain
 import org.matrix.android.sdk.internal.database.model.EventEntity
@@ -32,16 +35,28 @@ import javax.inject.Inject
 
 internal class EventInsertLiveObserver @Inject constructor(@SessionDatabase realmConfiguration: RealmConfiguration,
                                                            private val processors: Set<@JvmSuppressWildcards EventInsertLiveProcessor>) :
-    RealmLiveEntityObserver(realmConfiguration) {
+        RealmLiveEntityObserver(realmConfiguration) {
 
     override val query = Monarchy.Query {
         it.where(EventInsertEntity::class.java).equalTo(EventInsertEntityFields.CAN_BE_PROCESSED, true)
     }
 
+    private val onResultsChangedFlow = MutableSharedFlow>()
+
+    init {
+        onResultsChangedFlow
+                .onEach { handleChange(it) }
+                .launchIn(observerScope)
+    }
+
     override fun onChange(results: RealmResults) {
         if (!results.isLoaded || results.isEmpty()) {
             return
         }
+        observerScope.launch { onResultsChangedFlow.emit(results) }
+    }
+
+    private suspend fun handleChange(results: RealmResults) {
         val idsToDeleteAfterProcess = ArrayList()
         val filteredEvents = ArrayList(results.size)
         Timber.v("EventInsertEntity updated with ${results.size} results in db")
@@ -58,30 +73,29 @@ internal class EventInsertLiveObserver @Inject constructor(@SessionDatabase real
             }
             idsToDeleteAfterProcess.add(it.eventId)
         }
-        observerScope.launch {
-            awaitTransaction(realmConfiguration) { realm ->
-                Timber.v("##Transaction: There are ${filteredEvents.size} events to process ")
-                filteredEvents.forEach { eventInsert ->
-                    val eventId = eventInsert.eventId
-                    val event = EventEntity.where(realm, eventId).findFirst()
-                    if (event == null) {
-                        Timber.v("Event $eventId not found")
-                        return@forEach
-                    }
-                    val domainEvent = event.asDomain()
-                    processors.filter {
-                        it.shouldProcess(eventId, domainEvent.getClearType(), eventInsert.insertType)
-                    }.forEach {
-                        it.process(realm, domainEvent)
-                    }
+
+        awaitTransaction(realmConfiguration) { realm ->
+            Timber.v("##Transaction: There are ${filteredEvents.size} events to process ")
+            filteredEvents.forEach { eventInsert ->
+                val eventId = eventInsert.eventId
+                val event = EventEntity.where(realm, eventId).findFirst()
+                if (event == null) {
+                    Timber.v("Event $eventId not found")
+                    return@forEach
+                }
+                val domainEvent = event.asDomain()
+                processors.filter {
+                    it.shouldProcess(eventId, domainEvent.getClearType(), eventInsert.insertType)
+                }.forEach {
+                    it.process(realm, domainEvent)
                 }
-                realm.where(EventInsertEntity::class.java)
-                        .`in`(EventInsertEntityFields.EVENT_ID, idsToDeleteAfterProcess.toTypedArray())
-                        .findAll()
-                        .deleteAllFromRealm()
             }
-            processors.forEach { it.onPostProcess() }
+            realm.where(EventInsertEntity::class.java)
+                    .`in`(EventInsertEntityFields.EVENT_ID, idsToDeleteAfterProcess.toTypedArray())
+                    .findAll()
+                    .deleteAllFromRealm()
         }
+        processors.forEach { it.onPostProcess() }
     }
 
     private fun shouldProcess(eventInsertEntity: EventInsertEntity): Boolean {
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/RealmKeysUtils.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/RealmKeysUtils.kt
index 7c622146d3..392c1a7185 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/RealmKeysUtils.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/RealmKeysUtils.kt
@@ -53,7 +53,7 @@ internal class RealmKeysUtils @Inject constructor(context: Context,
     }
 
     /**
-     * Check if there is already a key for this alias
+     * Check if there is already a key for this alias.
      */
     private fun hasKeyForDatabase(alias: String): Boolean {
         return sharedPreferences.contains("${ENCRYPTED_KEY_PREFIX}_$alias")
@@ -77,8 +77,8 @@ internal class RealmKeysUtils @Inject constructor(context: Context,
     }
 
     /**
-     * Retrieves the key for this database
-     * throws if something goes wrong
+     * Retrieves the key for this database.
+     * Throws if something goes wrong.
      */
     private fun extractKeyForDatabase(alias: String): ByteArray {
         val encryptedB64 = sharedPreferences.getString("${ENCRYPTED_KEY_PREFIX}_$alias", null)
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/RealmLiveEntityObserver.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/RealmLiveEntityObserver.kt
index 50eb086f9a..f2f88e216b 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/RealmLiveEntityObserver.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/RealmLiveEntityObserver.kt
@@ -35,7 +35,7 @@ import java.util.concurrent.atomic.AtomicReference
 internal interface LiveEntityObserver : SessionLifecycleObserver
 
 internal abstract class RealmLiveEntityObserver(protected val realmConfiguration: RealmConfiguration) :
-    LiveEntityObserver, RealmChangeListener> {
+        LiveEntityObserver, RealmChangeListener> {
 
     private companion object {
         val BACKGROUND_HANDLER = createBackgroundHandler("LIVE_ENTITY_BACKGROUND")
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/RealmSessionProvider.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/RealmSessionProvider.kt
index 8c62c345d0..e5b5915590 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/RealmSessionProvider.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/RealmSessionProvider.kt
@@ -33,7 +33,7 @@ import kotlin.concurrent.getOrSet
  */
 @SessionScope
 internal class RealmSessionProvider @Inject constructor(@SessionDatabase private val monarchy: Monarchy) :
-    SessionLifecycleObserver {
+        SessionLifecycleObserver {
 
     private val realmThreadLocal = ThreadLocal()
 
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/RealmSessionStoreMigration.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/RealmSessionStoreMigration.kt
index a57397dad5..55bccfd1ec 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/RealmSessionStoreMigration.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/RealmSessionStoreMigration.kt
@@ -44,6 +44,8 @@ import org.matrix.android.sdk.internal.database.migration.MigrateSessionTo023
 import org.matrix.android.sdk.internal.database.migration.MigrateSessionTo024
 import org.matrix.android.sdk.internal.database.migration.MigrateSessionTo025
 import org.matrix.android.sdk.internal.database.migration.MigrateSessionTo026
+import org.matrix.android.sdk.internal.database.migration.MigrateSessionTo027
+import org.matrix.android.sdk.internal.database.migration.MigrateSessionTo028
 import org.matrix.android.sdk.internal.util.Normalizer
 import timber.log.Timber
 import javax.inject.Inject
@@ -52,13 +54,13 @@ internal class RealmSessionStoreMigration @Inject constructor(
         private val normalizer: Normalizer
 ) : RealmMigration {
     /**
-     * Forces all RealmSessionStoreMigration instances to be equal
-     * Avoids Realm throwing when multiple instances of the migration are set
+     * Forces all RealmSessionStoreMigration instances to be equal.
+     * Avoids Realm throwing when multiple instances of the migration are set.
      */
     override fun equals(other: Any?) = other is RealmSessionStoreMigration
     override fun hashCode() = 1000
 
-    val schemaVersion = 26L
+    val schemaVersion = 28L
 
     override fun migrate(realm: DynamicRealm, oldVersion: Long, newVersion: Long) {
         Timber.d("Migrating Realm Session from $oldVersion to $newVersion")
@@ -89,5 +91,7 @@ internal class RealmSessionStoreMigration @Inject constructor(
         if (oldVersion < 24) MigrateSessionTo024(realm).perform()
         if (oldVersion < 25) MigrateSessionTo025(realm).perform()
         if (oldVersion < 26) MigrateSessionTo026(realm).perform()
+        if (oldVersion < 27) MigrateSessionTo027(realm).perform()
+        if (oldVersion < 28) MigrateSessionTo028(realm).perform()
     }
 }
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/helper/ChunkEntityHelper.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/helper/ChunkEntityHelper.kt
index 4fdedabd70..caaf6b8110 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/helper/ChunkEntityHelper.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/helper/ChunkEntityHelper.kt
@@ -166,7 +166,7 @@ private fun ChunkEntity.addTimelineEventFromMerge(realm: Realm, timelineEventEnt
 }
 
 /**
- * Upon copy of the timeline events we should update the latestMessage TimelineEventEntity with the new one
+ * Upon copy of the timeline events we should update the latestMessage TimelineEventEntity with the new one.
  */
 private fun handleThreadSummary(realm: Realm, oldEventId: String, newTimelineEventEntity: TimelineEventEntity) {
     EventEntity
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/helper/ThreadEventsHelper.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/helper/ThreadEventsHelper.kt
index 04cf5b78af..db3647c3fa 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/helper/ThreadEventsHelper.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/helper/ThreadEventsHelper.kt
@@ -83,7 +83,7 @@ internal fun EventEntity.findRootThreadEvent(): EventEntity? =
         }
 
 /**
- * Mark or update the current event a root thread event
+ * Mark or update the current event a root thread event.
  */
 internal fun EventEntity.markEventAsRoot(
         inThreadMessages: Int,
@@ -94,8 +94,8 @@ internal fun EventEntity.markEventAsRoot(
 }
 
 /**
- * Count the number of threads for the provided root thread eventId, and finds the latest event message
- * note: Redactions are handled by RedactionEventProcessor
+ * Count the number of threads for the provided root thread eventId, and finds the latest event message.
+ * Note: Redactions are handled by RedactionEventProcessor.
  * @param rootThreadEventId The root eventId that will find the number of threads
  * @return A ThreadSummary containing the counted threads and the latest event message
  */
@@ -150,7 +150,7 @@ internal fun countInThreadMessages(realm: Realm, roomId: String, rootThreadEvent
                 }.size
 
 /**
- * Mapping string to UnsignedData using Moshi
+ * Mapping string to UnsignedData using Moshi.
  */
 private fun String.toUnsignedData(): UnsignedData? =
         try {
@@ -162,7 +162,7 @@ private fun String.toUnsignedData(): UnsignedData? =
 
 /**
  * Lets compare them in case user is moving forward in the timeline and we cannot know the
- * exact chunk sequence while currentChunk is not yet committed in the DB
+ * exact chunk sequence while currentChunk is not yet committed in the DB.
  */
 private fun findMostRecentEvent(result: TimelineEventEntity, currentChunkLatestEvent: TimelineEventEntity?): TimelineEventEntity {
     currentChunkLatestEvent ?: return result
@@ -175,7 +175,7 @@ private fun findMostRecentEvent(result: TimelineEventEntity, currentChunkLatestE
 }
 
 /**
- * Find the latest event of the current chunk
+ * Find the latest event of the current chunk.
  */
 private fun findLatestSortedChunkEvent(chunk: ChunkEntity, rootThreadEventId: String): TimelineEventEntity? =
         chunk.timelineEvents.sort(TimelineEventEntityFields.DISPLAY_INDEX, Sort.DESCENDING)?.firstOrNull {
@@ -183,7 +183,7 @@ private fun findLatestSortedChunkEvent(chunk: ChunkEntity, rootThreadEventId: St
         }
 
 /**
- * Find all TimelineEventEntity that are root threads for the specified room
+ * Find all TimelineEventEntity that are root threads for the specified room.
  * @param roomId The room that all stored root threads will be returned
  */
 internal fun TimelineEventEntity.Companion.findAllThreadsForRoomId(realm: Realm, roomId: String): RealmQuery =
@@ -194,7 +194,7 @@ internal fun TimelineEventEntity.Companion.findAllThreadsForRoomId(realm: Realm,
                 .sort("${TimelineEventEntityFields.ROOT.THREAD_SUMMARY_LATEST_MESSAGE}.${TimelineEventEntityFields.ROOT.ORIGIN_SERVER_TS}", Sort.DESCENDING)
 
 /**
- * Map each root thread TimelineEvent with the equivalent decrypted text edition/replacement
+ * Map each root thread TimelineEvent with the equivalent decrypted text edition/replacement.
  */
 internal fun List.mapEventsWithEdition(realm: Realm, roomId: String): List =
         this.map {
@@ -207,15 +207,17 @@ internal fun List.mapEventsWithEdition(realm: Realm, roomId: Stri
                     ?.eventId
                     ?.let { editedEventId ->
                         TimelineEventEntity.where(realm, roomId, eventId = editedEventId).findFirst()?.let { editedEvent ->
-                            it.root.threadDetails = it.root.threadDetails?.copy(lastRootThreadEdition = editedEvent.root?.asDomain()?.getDecryptedTextSummary()
-                                    ?: "(edited)")
+                            it.root.threadDetails = it.root.threadDetails?.copy(
+                                    lastRootThreadEdition = editedEvent.root?.asDomain()?.getDecryptedTextSummary()
+                                            ?: "(edited)"
+                            )
                             it
                         } ?: it
                     } ?: it
         }
 
 /**
- * Returns a list of all the marked unread threads that exists for the specified room
+ * Returns a list of all the marked unread threads that exists for the specified room.
  * @param roomId The roomId that the user is currently in
  */
 internal fun TimelineEventEntity.Companion.findAllLocalThreadNotificationsForRoomId(realm: Realm, roomId: String): RealmQuery =
@@ -229,7 +231,7 @@ internal fun TimelineEventEntity.Companion.findAllLocalThreadNotificationsForRoo
                 .endGroup()
 
 /**
- * Returns whether or not the given user is participating in a current thread
+ * Returns whether or not the given user is participating in a current thread.
  * @param roomId the room that the thread exists
  * @param rootThreadEventId the thread that the search will be done
  * @param senderId the user that will try to find participation
@@ -244,7 +246,7 @@ internal fun TimelineEventEntity.Companion.isUserParticipatingInThread(realm: Re
                 ?: false
 
 /**
- * Returns whether or not the given user is mentioned in a current thread
+ * Returns whether or not the given user is mentioned in a current thread.
  * @param roomId the room that the thread exists
  * @param rootThreadEventId the thread that the search will be done
  * @param userId the user that will try to find if there is a mention
@@ -260,7 +262,7 @@ internal fun TimelineEventEntity.Companion.isUserMentionedInThread(realm: Realm,
                 ?: false
 
 /**
- * Find the read receipt for the current user
+ * Find the read receipt for the current user.
  */
 internal fun findMyReadReceipt(realm: Realm, roomId: String, userId: String): String? =
         ReadReceiptEntity.where(realm, roomId = roomId, userId = userId)
@@ -268,7 +270,7 @@ internal fun findMyReadReceipt(realm: Realm, roomId: String, userId: String): St
                 ?.eventId
 
 /**
- * Returns whether or not the user is mentioned in the event
+ * Returns whether or not the user is mentioned in the event.
  */
 internal fun isUserMentioned(currentUserId: String, timelineEventEntity: TimelineEventEntity?): Boolean {
     return timelineEventEntity?.root?.asDomain()?.isUserMentioned(currentUserId) == true
@@ -341,7 +343,8 @@ internal fun updateNotificationsNew(roomId: String, realm: Realm, currentUserId:
                     realm = realm,
                     roomId = roomId,
                     rootThreadEventId = eventId,
-                    senderId = currentUserId)
+                    senderId = currentUserId
+            )
             val rootThreadEventEntity = EventEntity.where(realm, eventId).findFirst()
 
             if (isUserParticipating) {
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/helper/ThreadSummaryHelper.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/helper/ThreadSummaryHelper.kt
index 24de26eeea..3bf574c207 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/helper/ThreadSummaryHelper.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/helper/ThreadSummaryHelper.kt
@@ -20,6 +20,7 @@ import io.realm.Realm
 import io.realm.RealmQuery
 import io.realm.Sort
 import io.realm.kotlin.createObject
+import kotlinx.coroutines.runBlocking
 import org.matrix.android.sdk.api.session.crypto.CryptoService
 import org.matrix.android.sdk.api.session.crypto.MXCryptoError
 import org.matrix.android.sdk.api.session.crypto.model.OlmDecryptionResult
@@ -64,7 +65,7 @@ internal fun ThreadSummaryEntity.updateThreadSummary(
 }
 
 /**
- * Updates the root thread event properties
+ * Updates the root thread event properties.
  */
 internal fun ThreadSummaryEntity.updateThreadSummaryRootEvent(
         rootThreadEventEntity: EventEntity,
@@ -83,7 +84,7 @@ internal fun ThreadSummaryEntity.updateThreadSummaryRootEvent(
 }
 
 /**
- * Updates the latest thread event properties
+ * Updates the latest thread event properties.
  */
 internal fun ThreadSummaryEntity.updateThreadSummaryLatestEvent(
         latestThreadEventEntity: EventEntity?,
@@ -127,7 +128,7 @@ private fun EventEntity.toTimelineEventEntity(roomMemberContentsByUser: HashMap<
     return timelineEventEntity
 }
 
-internal suspend fun ThreadSummaryEntity.Companion.createOrUpdate(
+internal fun ThreadSummaryEntity.Companion.createOrUpdate(
         threadSummaryType: ThreadSummaryUpdateType,
         realm: Realm,
         roomId: String,
@@ -136,7 +137,8 @@ internal suspend fun ThreadSummaryEntity.Companion.createOrUpdate(
         roomMemberContentsByUser: HashMap,
         roomEntity: RoomEntity,
         userId: String,
-        cryptoService: CryptoService? = null
+        cryptoService: CryptoService? = null,
+        currentTimeMillis: Long,
 ) {
     when (threadSummaryType) {
         ThreadSummaryUpdateType.REPLACE -> {
@@ -152,11 +154,19 @@ internal suspend fun ThreadSummaryEntity.Companion.createOrUpdate(
                 Timber.i("###THREADS ThreadSummaryHelper REPLACE eventId:${it.rootThreadEventId} ")
             }
 
-            val rootThreadEventEntity = createEventEntity(roomId, rootThreadEvent, realm).also {
-                decryptIfNeeded(cryptoService, it, roomId)
+            val rootThreadEventEntity = createEventEntity(realm, roomId, rootThreadEvent, currentTimeMillis).also {
+                try {
+                    decryptIfNeeded(cryptoService, it, roomId)
+                } catch (e: InterruptedException) {
+                    Timber.i("Decryption got interrupted")
+                }
             }
-            val latestThreadEventEntity = createLatestEventEntity(roomId, rootThreadEvent, roomMemberContentsByUser, realm)?.also {
-                decryptIfNeeded(cryptoService, it, roomId)
+            val latestThreadEventEntity = createLatestEventEntity(realm, roomId, rootThreadEvent, roomMemberContentsByUser, currentTimeMillis)?.also {
+                try {
+                    decryptIfNeeded(cryptoService, it, roomId)
+                } catch (e: InterruptedException) {
+                    Timber.i("Decryption got interrupted")
+                }
             }
             val isUserParticipating = rootThreadEvent.unsignedData.relations.latestThread.isUserParticipating == true || rootThreadEvent.senderId == userId
             roomMemberContentsByUser.addSenderState(realm, roomId, rootThreadEvent.senderId)
@@ -204,14 +214,15 @@ internal suspend fun ThreadSummaryEntity.Companion.createOrUpdate(
     }
 }
 
-private suspend fun decryptIfNeeded(cryptoService: CryptoService?, eventEntity: EventEntity, roomId: String) {
+private fun decryptIfNeeded(cryptoService: CryptoService?, eventEntity: EventEntity, roomId: String) {
     cryptoService ?: return
     val event = eventEntity.asDomain()
     if (event.isEncrypted() && event.mxDecryptionResult == null && event.eventId != null) {
         try {
             Timber.i("###THREADS ThreadSummaryHelper request decryption for eventId:${event.eventId}")
             // Event from sync does not have roomId, so add it to the event first
-            val result = cryptoService.decryptEvent(event.copy(roomId = roomId), "")
+            // note: runBlocking should be used here while we are in realm single thread executor, to avoid thread switching
+            val result = runBlocking { cryptoService.decryptEvent(event.copy(roomId = roomId), "") }
             event.mxDecryptionResult = OlmDecryptionResult(
                     payload = result.clearEvent,
                     senderKey = result.senderCurve25519Key,
@@ -230,7 +241,7 @@ private suspend fun decryptIfNeeded(cryptoService: CryptoService?, eventEntity:
 }
 
 /**
- * Request decryption
+ * Request decryption.
  */
 private fun requestDecryption(eventDecryptor: TimelineEventDecryptor?, event: Event?) {
     eventDecryptor ?: return
@@ -244,7 +255,7 @@ private fun requestDecryption(eventDecryptor: TimelineEventDecryptor?, event: Ev
 }
 
 /**
- * If we don't have any new state on this user, get it from db
+ * If we don't have any new state on this user, get it from db.
  */
 private fun HashMap.addSenderState(realm: Realm, roomId: String, senderId: String) {
     getOrPut(senderId) {
@@ -256,10 +267,10 @@ private fun HashMap.addSenderState(realm: Realm, roo
 }
 
 /**
- * Create an EventEntity for the root thread event or get an existing one
+ * Create an EventEntity for the root thread event or get an existing one.
  */
-private fun createEventEntity(roomId: String, event: Event, realm: Realm): EventEntity {
-    val ageLocalTs = event.unsignedData?.age?.let { System.currentTimeMillis() - it }
+private fun createEventEntity(realm: Realm, roomId: String, event: Event, currentTimeMillis: Long): EventEntity {
+    val ageLocalTs = event.unsignedData?.age?.let { currentTimeMillis - it }
     return event.toEntity(roomId, SendState.SYNCED, ageLocalTs).copyToRealmOrIgnore(realm, EventInsertType.PAGINATION)
 }
 
@@ -268,28 +279,30 @@ private fun createEventEntity(roomId: String, event: Event, realm: Realm): Event
  * state
  */
 private fun createLatestEventEntity(
+        realm: Realm,
         roomId: String,
         rootThreadEvent: Event,
         roomMemberContentsByUser: HashMap,
-        realm: Realm): EventEntity? {
+        currentTimeMillis: Long,
+): EventEntity? {
     return getLatestEvent(rootThreadEvent)?.let {
         it.senderId?.let { senderId ->
             roomMemberContentsByUser.addSenderState(realm, roomId, senderId)
         }
-        createEventEntity(roomId, it, realm)
+        createEventEntity(realm, roomId, it, currentTimeMillis)
     }
 }
 
 /**
- * Returned the latest event message, if any
+ * Returned the latest event message, if any.
  */
 private fun getLatestEvent(rootThreadEvent: Event): Event? {
     return rootThreadEvent.unsignedData?.relations?.latestThread?.event
 }
 
 /**
- * Find all ThreadSummaryEntity for the specified roomId, sorted by origin server
- * note: Sorting cannot be provided by server, so we have to use that unstable property
+ * Find all ThreadSummaryEntity for the specified roomId, sorted by origin server.
+ * note: Sorting cannot be provided by server, so we have to use that unstable property.
  * @param roomId The id of the room
  */
 internal fun ThreadSummaryEntity.Companion.findAllThreadsForRoomId(realm: Realm, roomId: String): RealmQuery =
@@ -298,7 +311,7 @@ internal fun ThreadSummaryEntity.Companion.findAllThreadsForRoomId(realm: Realm,
                 .sort(ThreadSummaryEntityFields.LATEST_THREAD_EVENT_ENTITY.ORIGIN_SERVER_TS, Sort.DESCENDING)
 
 /**
- * Enhance each [ThreadSummary] root and latest event with the equivalent decrypted text edition/replacement
+ * Enhance each [ThreadSummary] root and latest event with the equivalent decrypted text edition/replacement.
  */
 internal fun List.enhanceWithEditions(realm: Realm, roomId: String): List =
         this.map {
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/helper/TimelineEventEntityHelper.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/helper/TimelineEventEntityHelper.kt
index ea508731b1..8a5d08cd30 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/helper/TimelineEventEntityHelper.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/helper/TimelineEventEntityHelper.kt
@@ -19,9 +19,10 @@ package org.matrix.android.sdk.internal.database.helper
 import io.realm.Realm
 import org.matrix.android.sdk.internal.database.model.TimelineEventEntity
 import org.matrix.android.sdk.internal.database.model.TimelineEventEntityFields
+import org.matrix.android.sdk.internal.database.query.where
 
 internal fun TimelineEventEntity.Companion.nextId(realm: Realm): Long {
-    val currentIdNum = realm.where(TimelineEventEntity::class.java).max(TimelineEventEntityFields.LOCAL_ID)
+    val currentIdNum = TimelineEventEntity.where(realm).max(TimelineEventEntityFields.LOCAL_ID)
     return if (currentIdNum == null) {
         1
     } else {
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/mapper/DraftMapper.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/mapper/DraftMapper.kt
index 737c4b4608..a00a2a8ee1 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/mapper/DraftMapper.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/mapper/DraftMapper.kt
@@ -20,7 +20,7 @@ import org.matrix.android.sdk.api.session.room.send.UserDraft
 import org.matrix.android.sdk.internal.database.model.DraftEntity
 
 /**
- * DraftEntity <-> UserDraft
+ * DraftEntity <-> UserDraft.
  */
 internal object DraftMapper {
 
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/mapper/EventAnnotationsSummaryMapper.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/mapper/EventAnnotationsSummaryMapper.kt
index 4a26b4c4bf..c747ad334f 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/mapper/EventAnnotationsSummaryMapper.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/mapper/EventAnnotationsSummaryMapper.kt
@@ -25,7 +25,6 @@ import org.matrix.android.sdk.internal.database.model.EventAnnotationsSummaryEnt
 internal object EventAnnotationsSummaryMapper {
     fun map(annotationsSummary: EventAnnotationsSummaryEntity): EventAnnotationsSummary {
         return EventAnnotationsSummary(
-                eventId = annotationsSummary.eventId,
                 reactionsSummary = annotationsSummary.reactionsSummary.toList().map {
                     ReactionAggregatedSummary(
                             it.key,
@@ -50,7 +49,6 @@ internal object EventAnnotationsSummaryMapper {
                         },
                 referencesAggregatedSummary = annotationsSummary.referencesSummaryEntity?.let {
                     ReferencesAggregatedSummary(
-                            it.eventId,
                             ContentMapper.map(it.content),
                             it.sourceEvents.toList(),
                             it.sourceLocalEcho.toList()
@@ -58,8 +56,10 @@ internal object EventAnnotationsSummaryMapper {
                 },
                 pollResponseSummary = annotationsSummary.pollResponseSummary?.let {
                     PollResponseAggregatedSummaryEntityMapper.map(it)
+                },
+                liveLocationShareAggregatedSummary = annotationsSummary.liveLocationShareAggregatedSummary?.let {
+                    LiveLocationShareAggregatedSummaryMapper.map(it)
                 }
-
         )
     }
 }
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/mapper/EventMapper.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/mapper/EventMapper.kt
index 3083df062e..bc7d40bf6f 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/mapper/EventMapper.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/mapper/EventMapper.kt
@@ -30,13 +30,14 @@ import org.matrix.android.sdk.api.session.threads.ThreadNotificationState
 import org.matrix.android.sdk.internal.database.model.EventEntity
 import org.matrix.android.sdk.internal.di.MoshiProvider
 import timber.log.Timber
+import kotlin.random.Random
 
 internal object EventMapper {
 
     fun map(event: Event, roomId: String): EventEntity {
         val eventEntity = EventEntity()
         // TODO change this as we shouldn't use event everywhere
-        eventEntity.eventId = event.eventId ?: "$$roomId-${System.currentTimeMillis()}-${event.hashCode()}"
+        eventEntity.eventId = event.eventId ?: "$$roomId-${Random.nextLong()}-${event.hashCode()}"
         eventEntity.roomId = event.roomId ?: roomId
         eventEntity.content = ContentMapper.map(event.content)
         eventEntity.prevContent = ContentMapper.map(event.resolvedPrevContent())
@@ -126,7 +127,10 @@ internal fun EventEntity.asDomain(castJsonNumbers: Boolean = false): Event {
     return EventMapper.map(this, castJsonNumbers)
 }
 
-internal fun Event.toEntity(roomId: String, sendState: SendState, ageLocalTs: Long?, contentToInject: String? = null): EventEntity {
+internal fun Event.toEntity(roomId: String,
+                            sendState: SendState,
+                            ageLocalTs: Long?,
+                            contentToInject: String? = null): EventEntity {
     return EventMapper.map(this, roomId).apply {
         this.sendState = sendState
         this.ageLocalTs = ageLocalTs
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/mapper/HomeServerCapabilitiesMapper.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/mapper/HomeServerCapabilitiesMapper.kt
index 2e33988a22..20af43530c 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/mapper/HomeServerCapabilitiesMapper.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/mapper/HomeServerCapabilitiesMapper.kt
@@ -28,7 +28,7 @@ import org.matrix.android.sdk.internal.session.homeserver.RoomVersions
 import org.matrix.android.sdk.internal.session.room.version.DefaultRoomVersionService
 
 /**
- * HomeServerCapabilitiesEntity -> HomeSeverCapabilities
+ * HomeServerCapabilitiesEntity -> HomeSeverCapabilities.
  */
 internal object HomeServerCapabilitiesMapper {
 
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/mapper/LiveLocationShareAggregatedSummaryMapper.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/mapper/LiveLocationShareAggregatedSummaryMapper.kt
new file mode 100644
index 0000000000..71b36f88bd
--- /dev/null
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/mapper/LiveLocationShareAggregatedSummaryMapper.kt
@@ -0,0 +1,33 @@
+/*
+ * Copyright (c) 2022 The Matrix.org Foundation C.I.C.
+ *
+ * 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 org.matrix.android.sdk.internal.database.mapper
+
+import org.matrix.android.sdk.api.session.events.model.toModel
+import org.matrix.android.sdk.api.session.room.model.livelocation.LiveLocationShareAggregatedSummary
+import org.matrix.android.sdk.api.session.room.model.message.MessageBeaconLocationDataContent
+import org.matrix.android.sdk.internal.database.model.livelocation.LiveLocationShareAggregatedSummaryEntity
+
+internal object LiveLocationShareAggregatedSummaryMapper {
+
+    fun map(entity: LiveLocationShareAggregatedSummaryEntity): LiveLocationShareAggregatedSummary {
+        return LiveLocationShareAggregatedSummary(
+                isActive = entity.isActive,
+                endOfLiveTimestampMillis = entity.endOfLiveTimestampMillis,
+                lastLocationDataContent = ContentMapper.map(entity.lastLocationContent).toModel()
+        )
+    }
+}
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/mapper/PushConditionMapper.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/mapper/PushConditionMapper.kt
index 5c0a2ba902..6521bf62d9 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/mapper/PushConditionMapper.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/mapper/PushConditionMapper.kt
@@ -16,7 +16,7 @@
 
 package org.matrix.android.sdk.internal.database.mapper
 
-import org.matrix.android.sdk.api.pushrules.rest.PushCondition
+import org.matrix.android.sdk.api.session.pushrules.rest.PushCondition
 import org.matrix.android.sdk.internal.database.model.PushConditionEntity
 
 internal object PushConditionMapper {
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/mapper/PushRulesMapper.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/mapper/PushRulesMapper.kt
index 12eff8efa1..0b07754126 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/mapper/PushRulesMapper.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/mapper/PushRulesMapper.kt
@@ -17,9 +17,9 @@ package org.matrix.android.sdk.internal.database.mapper
 
 import com.squareup.moshi.Types
 import io.realm.RealmList
-import org.matrix.android.sdk.api.pushrules.Kind
-import org.matrix.android.sdk.api.pushrules.rest.PushCondition
-import org.matrix.android.sdk.api.pushrules.rest.PushRule
+import org.matrix.android.sdk.api.session.pushrules.Kind
+import org.matrix.android.sdk.api.session.pushrules.rest.PushCondition
+import org.matrix.android.sdk.api.session.pushrules.rest.PushRule
 import org.matrix.android.sdk.internal.database.model.PushRuleEntity
 import org.matrix.android.sdk.internal.di.MoshiProvider
 import timber.log.Timber
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/migration/MigrateSessionTo019.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/migration/MigrateSessionTo019.kt
index d63ef62889..754a66bb4b 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/migration/MigrateSessionTo019.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/migration/MigrateSessionTo019.kt
@@ -22,7 +22,7 @@ import org.matrix.android.sdk.internal.util.Normalizer
 import org.matrix.android.sdk.internal.util.database.RealmMigrator
 
 internal class MigrateSessionTo019(realm: DynamicRealm,
-                          private val normalizer: Normalizer) : RealmMigrator(realm, 19) {
+                                   private val normalizer: Normalizer) : RealmMigrator(realm, 19) {
 
     override fun doMigrate(realm: DynamicRealm) {
         realm.schema.get("RoomSummaryEntity")
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/migration/MigrateSessionTo026.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/migration/MigrateSessionTo026.kt
index 35a6135ba2..5bab93cb04 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/migration/MigrateSessionTo026.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/migration/MigrateSessionTo026.kt
@@ -29,7 +29,7 @@ import org.matrix.android.sdk.internal.util.database.RealmMigrator
 /**
  * Migrating to:
  * Live thread list: using enhanced /messages api MSC3440
- * Live thread timeline: using /relations api
+ * Live thread timeline: using /relations api.
  */
 internal class MigrateSessionTo026(realm: DynamicRealm) : RealmMigrator(realm, 26) {
 
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/migration/MigrateSessionTo027.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/migration/MigrateSessionTo027.kt
new file mode 100644
index 0000000000..40189c0f46
--- /dev/null
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/migration/MigrateSessionTo027.kt
@@ -0,0 +1,46 @@
+/*
+ * Copyright (c) 2022 The Matrix.org Foundation C.I.C.
+ *
+ * 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 org.matrix.android.sdk.internal.database.migration
+
+import io.realm.DynamicRealm
+import io.realm.FieldAttribute
+import org.matrix.android.sdk.internal.database.model.EventAnnotationsSummaryEntityFields
+import org.matrix.android.sdk.internal.database.model.livelocation.LiveLocationShareAggregatedSummaryEntityFields
+import org.matrix.android.sdk.internal.util.database.RealmMigrator
+
+/**
+ * Migrating to:
+ * Live location sharing aggregated summary.
+ */
+internal class MigrateSessionTo027(realm: DynamicRealm) : RealmMigrator(realm, 27) {
+
+    override fun doMigrate(realm: DynamicRealm) {
+        val liveLocationSummaryEntity = realm.schema.get("LiveLocationShareAggregatedSummaryEntity")
+                ?: realm.schema.create("LiveLocationShareAggregatedSummaryEntity")
+                        .addField(LiveLocationShareAggregatedSummaryEntityFields.EVENT_ID, String::class.java, FieldAttribute.REQUIRED)
+                        .addField(LiveLocationShareAggregatedSummaryEntityFields.ROOM_ID, String::class.java, FieldAttribute.REQUIRED)
+                        .addField(LiveLocationShareAggregatedSummaryEntityFields.IS_ACTIVE, Boolean::class.java)
+                        .setNullable(LiveLocationShareAggregatedSummaryEntityFields.IS_ACTIVE, true)
+                        .addField(LiveLocationShareAggregatedSummaryEntityFields.END_OF_LIVE_TIMESTAMP_MILLIS, Long::class.java)
+                        .setNullable(LiveLocationShareAggregatedSummaryEntityFields.END_OF_LIVE_TIMESTAMP_MILLIS, true)
+                        .addField(LiveLocationShareAggregatedSummaryEntityFields.LAST_LOCATION_CONTENT, String::class.java)
+                ?: return
+
+        realm.schema.get("EventAnnotationsSummaryEntity")
+                ?.addRealmObjectField(EventAnnotationsSummaryEntityFields.LIVE_LOCATION_SHARE_AGGREGATED_SUMMARY.`$`, liveLocationSummaryEntity)
+    }
+}
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/migration/MigrateSessionTo028.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/migration/MigrateSessionTo028.kt
new file mode 100644
index 0000000000..ed9b90d288
--- /dev/null
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/migration/MigrateSessionTo028.kt
@@ -0,0 +1,34 @@
+/*
+ * Copyright (c) 2022 The Matrix.org Foundation C.I.C.
+ *
+ * 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 org.matrix.android.sdk.internal.database.migration
+
+import io.realm.DynamicRealm
+import org.matrix.android.sdk.internal.database.model.livelocation.LiveLocationShareAggregatedSummaryEntityFields
+import org.matrix.android.sdk.internal.util.database.RealmMigrator
+
+/**
+ * Migrating to:
+ * Live location sharing aggregated summary.
+ */
+internal class MigrateSessionTo028(realm: DynamicRealm) : RealmMigrator(realm, 28) {
+
+    override fun doMigrate(realm: DynamicRealm) {
+        realm.schema.get("LiveLocationShareAggregatedSummaryEntity")
+                ?.takeIf { !it.hasPrimaryKey() }
+                ?.addPrimaryKey(LiveLocationShareAggregatedSummaryEntityFields.EVENT_ID)
+    }
+}
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/ChunkEntity.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/ChunkEntity.kt
index 88eb821aa9..8399d82d5d 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/ChunkEntity.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/ChunkEntity.kt
@@ -24,19 +24,20 @@ import io.realm.annotations.LinkingObjects
 import org.matrix.android.sdk.internal.extensions.assertIsManaged
 import org.matrix.android.sdk.internal.extensions.clearWith
 
-internal open class ChunkEntity(@Index var prevToken: String? = null,
+internal open class ChunkEntity(
+        @Index var prevToken: String? = null,
         // Because of gaps we can have several chunks with nextToken == null
-                                @Index var nextToken: String? = null,
-                                var prevChunk: ChunkEntity? = null,
-                                var nextChunk: ChunkEntity? = null,
-                                var stateEvents: RealmList = RealmList(),
-                                var timelineEvents: RealmList = RealmList(),
+        @Index var nextToken: String? = null,
+        var prevChunk: ChunkEntity? = null,
+        var nextChunk: ChunkEntity? = null,
+        var stateEvents: RealmList = RealmList(),
+        var timelineEvents: RealmList = RealmList(),
         // Only one chunk will have isLastForward == true
-                                @Index var isLastForward: Boolean = false,
-                                @Index var isLastBackward: Boolean = false,
+        @Index var isLastForward: Boolean = false,
+        @Index var isLastBackward: Boolean = false,
         // Threads
-                                @Index var rootThreadEventId: String? = null,
-                                @Index var isLastForwardThread: Boolean = false,
+        @Index var rootThreadEventId: String? = null,
+        @Index var isLastForwardThread: Boolean = false,
 ) : RealmObject() {
 
     fun identifier() = "${prevToken}_$nextToken"
@@ -68,7 +69,7 @@ internal fun ChunkEntity.deleteOnCascade(
 }
 
 /**
- * Delete the chunk along with the thread events that were temporarily created
+ * Delete the chunk along with the thread events that were temporarily created.
  */
 internal fun ChunkEntity.deleteAndClearThreadEvents() {
     assertIsManaged()
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/EditAggregatedSummaryEntity.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/EditAggregatedSummaryEntity.kt
index 0ed927a6b8..61acd51dd4 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/EditAggregatedSummaryEntity.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/EditAggregatedSummaryEntity.kt
@@ -20,7 +20,7 @@ import io.realm.RealmObject
 import io.realm.annotations.RealmClass
 
 /**
- * Keep all the editions of a message
+ * Keep all the editions of a message.
  */
 internal open class EditAggregatedSummaryEntity(
         // The list of the editions used to build the summary (might be out of sync if chunked received from message chunk)
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/EventAnnotationsSummaryEntity.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/EventAnnotationsSummaryEntity.kt
index 3e88130420..645998d0c0 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/EventAnnotationsSummaryEntity.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/EventAnnotationsSummaryEntity.kt
@@ -18,6 +18,7 @@ package org.matrix.android.sdk.internal.database.model
 import io.realm.RealmList
 import io.realm.RealmObject
 import io.realm.annotations.PrimaryKey
+import org.matrix.android.sdk.internal.database.model.livelocation.LiveLocationShareAggregatedSummaryEntity
 import timber.log.Timber
 
 internal open class EventAnnotationsSummaryEntity(
@@ -27,11 +28,12 @@ internal open class EventAnnotationsSummaryEntity(
         var reactionsSummary: RealmList = RealmList(),
         var editSummary: EditAggregatedSummaryEntity? = null,
         var referencesSummaryEntity: ReferencesAggregatedSummaryEntity? = null,
-        var pollResponseSummary: PollResponseAggregatedSummaryEntity? = null
+        var pollResponseSummary: PollResponseAggregatedSummaryEntity? = null,
+        var liveLocationShareAggregatedSummary: LiveLocationShareAggregatedSummaryEntity? = null,
 ) : RealmObject() {
 
     /**
-     * Cleanup undesired editions, done by users different from the originalEventSender
+     * Cleanup undesired editions, done by users different from the originalEventSender.
      */
     fun cleanUp(originalEventSenderId: String?) {
         originalEventSenderId ?: return
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/FilterEntity.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/FilterEntity.kt
index 96014d29ad..e138e305c7 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/FilterEntity.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/FilterEntity.kt
@@ -19,8 +19,8 @@ package org.matrix.android.sdk.internal.database.model
 import io.realm.RealmObject
 
 /**
- * Contain a map between Json filter string and filterId (from Homeserver)
- * Currently there is only one object in this table
+ * Contain a map between Json filter string and filterId (from Homeserver).
+ * Currently there is only one object in this table.
  */
 internal open class FilterEntity(
         // The serialized FilterBody
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/GroupEntity.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/GroupEntity.kt
index 527f782359..0120bb91d3 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/GroupEntity.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/GroupEntity.kt
@@ -25,7 +25,7 @@ import org.matrix.android.sdk.api.session.room.model.Membership
  * Then GetGroupDataTask is called regularly to fetch group information from the homeserver.
  */
 internal open class GroupEntity(@PrimaryKey var groupId: String = "") :
-    RealmObject() {
+        RealmObject() {
 
     private var membershipStr: String = Membership.NONE.name
     var membership: Membership
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/PendingThreePidEntity.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/PendingThreePidEntity.kt
index 98c38c8969..bcf64e0da8 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/PendingThreePidEntity.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/PendingThreePidEntity.kt
@@ -19,7 +19,7 @@ package org.matrix.android.sdk.internal.database.model
 import io.realm.RealmObject
 
 /**
- * This class is used to store pending threePid data, when user wants to add a threePid to his account
+ * This class is used to store pending threePid data, when user wants to add a threePid to his account.
  */
 internal open class PendingThreePidEntity(
         var email: String? = null,
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/PollResponseAggregatedSummaryEntity.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/PollResponseAggregatedSummaryEntity.kt
index 0000a558ac..d759bd3cd9 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/PollResponseAggregatedSummaryEntity.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/PollResponseAggregatedSummaryEntity.kt
@@ -19,7 +19,7 @@ import io.realm.RealmList
 import io.realm.RealmObject
 
 /**
- * Keep the latest state of a poll
+ * Keep the latest state of a poll.
  */
 internal open class PollResponseAggregatedSummaryEntity(
         // For now we persist this a JSON for greater flexibility
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/PushRulesEntity.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/PushRulesEntity.kt
index 4125d90891..62bf40c1d2 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/PushRulesEntity.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/PushRulesEntity.kt
@@ -17,7 +17,7 @@ package org.matrix.android.sdk.internal.database.model
 
 import io.realm.RealmList
 import io.realm.RealmObject
-import org.matrix.android.sdk.api.pushrules.RuleKind
+import org.matrix.android.sdk.api.session.pushrules.RuleKind
 import org.matrix.android.sdk.internal.extensions.clearWith
 
 internal open class PushRulesEntity(
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/RoomEntity.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/RoomEntity.kt
index 4a6f6a7bf8..d8e6b8af0f 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/RoomEntity.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/RoomEntity.kt
@@ -48,8 +48,10 @@ internal open class RoomEntity(@PrimaryKey var roomId: String = "",
         set(value) {
             membersLoadStatusStr = value.name
         }
+
     companion object
 }
+
 internal fun RoomEntity.removeThreadSummaryIfNeeded(eventId: String) {
     assertIsManaged()
     threadSummaries.findRootOrLatest(eventId)?.let {
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/SessionRealmModule.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/SessionRealmModule.kt
index d0d23dd491..890c2300f8 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/SessionRealmModule.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/SessionRealmModule.kt
@@ -17,13 +17,15 @@
 package org.matrix.android.sdk.internal.database.model
 
 import io.realm.annotations.RealmModule
+import org.matrix.android.sdk.internal.database.model.livelocation.LiveLocationShareAggregatedSummaryEntity
 import org.matrix.android.sdk.internal.database.model.presence.UserPresenceEntity
 import org.matrix.android.sdk.internal.database.model.threads.ThreadSummaryEntity
 
 /**
- * Realm module for Session
+ * Realm module for Session.
  */
-@RealmModule(library = true,
+@RealmModule(
+        library = true,
         classes = [
             ChunkEntity::class,
             EventEntity::class,
@@ -47,6 +49,7 @@ import org.matrix.android.sdk.internal.database.model.threads.ThreadSummaryEntit
             EditAggregatedSummaryEntity::class,
             EditionOfEvent::class,
             PollResponseAggregatedSummaryEntity::class,
+            LiveLocationShareAggregatedSummaryEntity::class,
             ReferencesAggregatedSummaryEntity::class,
             PushRulesEntity::class,
             PushRuleEntity::class,
@@ -69,5 +72,6 @@ import org.matrix.android.sdk.internal.database.model.threads.ThreadSummaryEntit
             SpaceParentSummaryEntity::class,
             UserPresenceEntity::class,
             ThreadSummaryEntity::class
-        ])
+        ]
+)
 internal class SessionRealmModule
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/TimelineEventEntity.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/TimelineEventEntity.kt
index aacd6570bc..477c04fe51 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/TimelineEventEntity.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/TimelineEventEntity.kt
@@ -32,8 +32,8 @@ internal open class TimelineEventEntity(var localId: Long = 0,
                                         var isUniqueDisplayName: Boolean = false,
                                         var senderAvatar: String? = null,
                                         var senderMembershipEventId: String? = null,
-                                        // ownedByThreadChunk indicates that the current TimelineEventEntity belongs
-                                        // to a thread chunk and is a temporarily event.
+        // ownedByThreadChunk indicates that the current TimelineEventEntity belongs
+        // to a thread chunk and is a temporarily event.
                                         var ownedByThreadChunk: Boolean = false,
                                         var readReceipts: ReadReceiptsSummaryEntity? = null
 ) : RealmObject() {
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/UserDraftsEntity.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/UserDraftsEntity.kt
index 06a6349350..2c778f9797 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/UserDraftsEntity.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/UserDraftsEntity.kt
@@ -22,7 +22,7 @@ import io.realm.RealmResults
 import io.realm.annotations.LinkingObjects
 
 /**
- * Create a specific table to be able to do direct query on it and keep the draft ordered
+ * Create a specific table to be able to do direct query on it and keep the draft ordered.
  */
 internal open class UserDraftsEntity(var userDrafts: RealmList = RealmList()
 ) : RealmObject() {
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/livelocation/LiveLocationShareAggregatedSummaryEntity.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/livelocation/LiveLocationShareAggregatedSummaryEntity.kt
new file mode 100644
index 0000000000..4d0d2c5c64
--- /dev/null
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/livelocation/LiveLocationShareAggregatedSummaryEntity.kt
@@ -0,0 +1,45 @@
+/*
+ * Copyright (c) 2022 The Matrix.org Foundation C.I.C.
+ *
+ * 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 org.matrix.android.sdk.internal.database.model.livelocation
+
+import io.realm.RealmObject
+import io.realm.annotations.PrimaryKey
+
+/**
+ * Aggregation info concerning a live location share.
+ */
+internal open class LiveLocationShareAggregatedSummaryEntity(
+        /**
+         * Event id of the event that started the live.
+         */
+        @PrimaryKey
+        var eventId: String = "",
+
+        var roomId: String = "",
+
+        var isActive: Boolean? = null,
+
+        var endOfLiveTimestampMillis: Long? = null,
+
+        /**
+         * For now we persist this as a JSON for greater flexibility.
+         * @see [org.matrix.android.sdk.api.session.room.model.message.MessageBeaconLocationDataContent]
+         */
+        var lastLocationContent: String? = null,
+) : RealmObject() {
+    companion object
+}
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/query/ChunkEntityQueries.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/query/ChunkEntityQueries.kt
index a33ba82f7a..9350102137 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/query/ChunkEntityQueries.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/query/ChunkEntityQueries.kt
@@ -56,18 +56,21 @@ internal fun ChunkEntity.Companion.findLastForwardChunkOfRoom(realm: Realm, room
             .equalTo(ChunkEntityFields.IS_LAST_FORWARD, true)
             .findFirst()
 }
+
 internal fun ChunkEntity.Companion.findLastForwardChunkOfThread(realm: Realm, roomId: String, rootThreadEventId: String): ChunkEntity? {
     return where(realm, roomId)
             .equalTo(ChunkEntityFields.ROOT_THREAD_EVENT_ID, rootThreadEventId)
             .equalTo(ChunkEntityFields.IS_LAST_FORWARD_THREAD, true)
             .findFirst()
 }
+
 internal fun ChunkEntity.Companion.findEventInThreadChunk(realm: Realm, roomId: String, event: String): ChunkEntity? {
     return where(realm, roomId)
             .`in`(ChunkEntityFields.TIMELINE_EVENTS.EVENT_ID, arrayListOf(event).toTypedArray())
             .equalTo(ChunkEntityFields.IS_LAST_FORWARD_THREAD, true)
             .findFirst()
 }
+
 internal fun ChunkEntity.Companion.findAllIncludingEvents(realm: Realm, eventIds: List): RealmResults {
     return realm.where()
             .`in`(ChunkEntityFields.TIMELINE_EVENTS.EVENT_ID, eventIds.toTypedArray())
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/query/FilterEntityQueries.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/query/FilterEntityQueries.kt
index 3968169e08..903282bac4 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/query/FilterEntityQueries.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/query/FilterEntityQueries.kt
@@ -23,14 +23,14 @@ import org.matrix.android.sdk.internal.database.model.FilterEntity
 import org.matrix.android.sdk.internal.session.filter.FilterFactory
 
 /**
- * Get the current filter
+ * Get the current filter.
  */
 internal fun FilterEntity.Companion.get(realm: Realm): FilterEntity? {
     return realm.where().findFirst()
 }
 
 /**
- * Get the current filter, create one if it does not exist
+ * Get the current filter, create one if it does not exist.
  */
 internal fun FilterEntity.Companion.getOrCreate(realm: Realm): FilterEntity {
     return get(realm) ?: realm.createObject()
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/query/HomeServerCapabilitiesQueries.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/query/HomeServerCapabilitiesQueries.kt
index 4f8ac20e94..2cb6faafb7 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/query/HomeServerCapabilitiesQueries.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/query/HomeServerCapabilitiesQueries.kt
@@ -22,14 +22,14 @@ import io.realm.kotlin.where
 import org.matrix.android.sdk.internal.database.model.HomeServerCapabilitiesEntity
 
 /**
- * Get the current HomeServerCapabilitiesEntity, return null if it does not exist
+ * Get the current HomeServerCapabilitiesEntity, return null if it does not exist.
  */
 internal fun HomeServerCapabilitiesEntity.Companion.get(realm: Realm): HomeServerCapabilitiesEntity? {
     return realm.where().findFirst()
 }
 
 /**
- * Get the current HomeServerCapabilitiesEntity, create one if it does not exist
+ * Get the current HomeServerCapabilitiesEntity, create one if it does not exist.
  */
 internal fun HomeServerCapabilitiesEntity.Companion.getOrCreate(realm: Realm): HomeServerCapabilitiesEntity {
     return get(realm) ?: realm.createObject()
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/query/LiveLocationShareAggregatedSummaryEntityQuery.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/query/LiveLocationShareAggregatedSummaryEntityQuery.kt
new file mode 100644
index 0000000000..2e2e939fa2
--- /dev/null
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/query/LiveLocationShareAggregatedSummaryEntityQuery.kt
@@ -0,0 +1,57 @@
+/*
+ * Copyright (c) 2022 The Matrix.org Foundation C.I.C.
+ *
+ * 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 org.matrix.android.sdk.internal.database.query
+
+import io.realm.Realm
+import io.realm.RealmQuery
+import io.realm.kotlin.where
+import org.matrix.android.sdk.internal.database.model.EventAnnotationsSummaryEntity
+import org.matrix.android.sdk.internal.database.model.livelocation.LiveLocationShareAggregatedSummaryEntity
+import org.matrix.android.sdk.internal.database.model.livelocation.LiveLocationShareAggregatedSummaryEntityFields
+
+internal fun LiveLocationShareAggregatedSummaryEntity.Companion.where(
+        realm: Realm,
+        roomId: String,
+        eventId: String,
+): RealmQuery {
+    return realm.where()
+            .equalTo(LiveLocationShareAggregatedSummaryEntityFields.ROOM_ID, roomId)
+            .equalTo(LiveLocationShareAggregatedSummaryEntityFields.EVENT_ID, eventId)
+}
+
+internal fun LiveLocationShareAggregatedSummaryEntity.Companion.create(
+        realm: Realm,
+        roomId: String,
+        eventId: String,
+): LiveLocationShareAggregatedSummaryEntity {
+    val obj = realm.createObject(LiveLocationShareAggregatedSummaryEntity::class.java, eventId).apply {
+        this.roomId = roomId
+    }
+    val annotationSummary = EventAnnotationsSummaryEntity.getOrCreate(realm, roomId = roomId, eventId = eventId)
+    annotationSummary.liveLocationShareAggregatedSummary = obj
+
+    return obj
+}
+
+internal fun LiveLocationShareAggregatedSummaryEntity.Companion.getOrCreate(
+        realm: Realm,
+        roomId: String,
+        eventId: String,
+): LiveLocationShareAggregatedSummaryEntity {
+    return LiveLocationShareAggregatedSummaryEntity.where(realm, roomId, eventId).findFirst()
+            ?: LiveLocationShareAggregatedSummaryEntity.create(realm, roomId, eventId)
+}
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/query/PreviewUrlCacheEntityQueries.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/query/PreviewUrlCacheEntityQueries.kt
index a139c17439..32ea7a1ba6 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/query/PreviewUrlCacheEntityQueries.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/query/PreviewUrlCacheEntityQueries.kt
@@ -23,7 +23,7 @@ import org.matrix.android.sdk.internal.database.model.PreviewUrlCacheEntity
 import org.matrix.android.sdk.internal.database.model.PreviewUrlCacheEntityFields
 
 /**
- * Get the current PreviewUrlCacheEntity, return null if it does not exist
+ * Get the current PreviewUrlCacheEntity, return null if it does not exist.
  */
 internal fun PreviewUrlCacheEntity.Companion.get(realm: Realm, url: String): PreviewUrlCacheEntity? {
     return realm.where()
@@ -32,7 +32,7 @@ internal fun PreviewUrlCacheEntity.Companion.get(realm: Realm, url: String): Pre
 }
 
 /**
- * Get the current PreviewUrlCacheEntity, create one if it does not exist
+ * Get the current PreviewUrlCacheEntity, create one if it does not exist.
  */
 internal fun PreviewUrlCacheEntity.Companion.getOrCreate(realm: Realm, url: String): PreviewUrlCacheEntity {
     return get(realm, url) ?: realm.createObject(url)
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/query/PushersQueries.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/query/PushersQueries.kt
index 1f6b210252..3cea19a690 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/query/PushersQueries.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/query/PushersQueries.kt
@@ -18,7 +18,7 @@ package org.matrix.android.sdk.internal.database.query
 import io.realm.Realm
 import io.realm.RealmQuery
 import io.realm.kotlin.where
-import org.matrix.android.sdk.api.pushrules.RuleKind
+import org.matrix.android.sdk.api.session.pushrules.RuleKind
 import org.matrix.android.sdk.internal.database.model.PushRuleEntity
 import org.matrix.android.sdk.internal.database.model.PushRuleEntityFields
 import org.matrix.android.sdk.internal.database.model.PushRulesEntity
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/query/RawCacheQueries.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/query/RawCacheQueries.kt
index ac5e29e1de..634c3b26f2 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/query/RawCacheQueries.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/query/RawCacheQueries.kt
@@ -23,7 +23,7 @@ import org.matrix.android.sdk.internal.database.model.RawCacheEntity
 import org.matrix.android.sdk.internal.database.model.RawCacheEntityFields
 
 /**
- * Get the current RawCacheEntity, return null if it does not exist
+ * Get the current RawCacheEntity, return null if it does not exist.
  */
 internal fun RawCacheEntity.Companion.get(realm: Realm, url: String): RawCacheEntity? {
     return realm.where()
@@ -32,7 +32,7 @@ internal fun RawCacheEntity.Companion.get(realm: Realm, url: String): RawCacheEn
 }
 
 /**
- * Get the current RawCacheEntity, create one if it does not exist
+ * Get the current RawCacheEntity, create one if it does not exist.
  */
 internal fun RawCacheEntity.Companion.getOrCreate(realm: Realm, url: String): RawCacheEntity {
     return get(realm, url) ?: realm.createObject(url)
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/query/ReadQueries.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/query/ReadQueries.kt
index 6c587dfcae..1a832a29bb 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/query/ReadQueries.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/query/ReadQueries.kt
@@ -61,7 +61,7 @@ private fun TimelineEventEntity.isBeforeLatestReadReceipt(realm: Realm, roomId:
 
 /**
  * Missing events can be caused by the latest timeline chunk no longer contain an older event or
- * by fast lane eagerly displaying events before the database has finished updating
+ * by fast lane eagerly displaying events before the database has finished updating.
  */
 private fun hasReadMissingEvent(realm: Realm, latestChunkEntity: ChunkEntity, roomId: String, userId: String, eventId: String): Boolean {
     return realm.doesEventExistInChunkHistory(eventId) && realm.hasReadReceiptInLatestChunk(latestChunkEntity, roomId, userId)
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/query/ThreadSummaryEntityQueries.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/query/ThreadSummaryEntityQueries.kt
index 517d43d7cf..eab2740433 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/query/ThreadSummaryEntityQueries.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/query/ThreadSummaryEntityQueries.kt
@@ -39,9 +39,11 @@ internal fun ThreadSummaryEntity.Companion.getOrCreate(realm: Realm, roomId: Str
         this.rootThreadEventId = rootThreadEventId
     }
 }
+
 internal fun ThreadSummaryEntity.Companion.getOrNull(realm: Realm, roomId: String, rootThreadEventId: String): ThreadSummaryEntity? {
     return where(realm, roomId, rootThreadEventId).findFirst()
 }
+
 internal fun RealmList.find(rootThreadEventId: String): ThreadSummaryEntity? {
     return this.where()
             .equalTo(ThreadSummaryEntityFields.ROOT_THREAD_EVENT_ID, rootThreadEventId)
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/query/TimelineEventEntityQueries.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/query/TimelineEventEntityQueries.kt
index 81d5ac835f..1654a33806 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/query/TimelineEventEntityQueries.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/query/TimelineEventEntityQueries.kt
@@ -29,26 +29,35 @@ import org.matrix.android.sdk.internal.database.model.RoomEntity
 import org.matrix.android.sdk.internal.database.model.TimelineEventEntity
 import org.matrix.android.sdk.internal.database.model.TimelineEventEntityFields
 
-internal fun TimelineEventEntity.Companion.where(realm: Realm, roomId: String, eventId: String): RealmQuery {
-    return realm.where()
+internal fun TimelineEventEntity.Companion.where(realm: Realm): RealmQuery {
+    return realm.where()
+}
+
+internal fun TimelineEventEntity.Companion.where(realm: Realm,
+                                                 roomId: String,
+                                                 eventId: String): RealmQuery {
+    return where(realm)
             .equalTo(TimelineEventEntityFields.ROOM_ID, roomId)
             .equalTo(TimelineEventEntityFields.EVENT_ID, eventId)
 }
 
-internal fun TimelineEventEntity.Companion.where(realm: Realm, roomId: String, eventIds: List): RealmQuery {
-    return realm.where()
+internal fun TimelineEventEntity.Companion.where(realm: Realm,
+                                                 roomId: String,
+                                                 eventIds: List): RealmQuery {
+    return where(realm)
             .equalTo(TimelineEventEntityFields.ROOM_ID, roomId)
             .`in`(TimelineEventEntityFields.EVENT_ID, eventIds.toTypedArray())
 }
 
 internal fun TimelineEventEntity.Companion.whereRoomId(realm: Realm,
                                                        roomId: String): RealmQuery {
-    return realm.where()
+    return where(realm)
             .equalTo(TimelineEventEntityFields.ROOM_ID, roomId)
 }
 
-internal fun TimelineEventEntity.Companion.findWithSenderMembershipEvent(realm: Realm, senderMembershipEventId: String): List {
-    return realm.where()
+internal fun TimelineEventEntity.Companion.findWithSenderMembershipEvent(realm: Realm,
+                                                                         senderMembershipEventId: String): List {
+    return where(realm)
             .equalTo(TimelineEventEntityFields.SENDER_MEMBERSHIP_EVENT_ID, senderMembershipEventId)
             .findAll()
 }
@@ -110,12 +119,12 @@ internal fun RealmQuery.filterTypes(filterTypes: List.find(eventId: String): TimelineEventEntity? {
-    return this.where()
+    return where()
             .equalTo(TimelineEventEntityFields.EVENT_ID, eventId)
             .findFirst()
 }
@@ -132,3 +141,14 @@ internal fun RealmQuery.filterSendStates(sendStates: List): RealmResults {
+    return where(realm)
+            .`in`(TimelineEventEntityFields.ROOT.SENDER, senderIds.toTypedArray())
+            .isNull(TimelineEventEntityFields.ROOT.STATE_KEY)
+            .findAll()
+}
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/query/TimelineEventFilter.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/query/TimelineEventFilter.kt
index a7317506a0..7a65623b76 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/query/TimelineEventFilter.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/query/TimelineEventFilter.kt
@@ -17,11 +17,11 @@
 package org.matrix.android.sdk.internal.database.query
 
 /**
- * Query strings used to filter the timeline events regarding the Json raw string of the Event
+ * Query strings used to filter the timeline events regarding the Json raw string of the Event.
  */
 internal object TimelineEventFilter {
     /**
-     * To apply to Event.content
+     * To apply to Event.content.
      */
     internal object Content {
         internal const val EDIT = """{*"m.relates_to"*"rel_type":*"m.replace"*}"""
@@ -30,14 +30,14 @@ internal object TimelineEventFilter {
     }
 
     /**
-     * To apply to Event.decryptionResultJson
+     * To apply to Event.decryptionResultJson.
      */
     internal object DecryptedContent {
         internal const val URL = """{*"file":*"url":*}"""
     }
 
     /**
-     * To apply to Event.unsigned
+     * To apply to Event.unsigned.
      */
     internal object Unsigned {
         internal const val REDACTED = """{*"redacted_because":*}"""
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/tools/RealmDebugTools.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/tools/RealmDebugTools.kt
index 103e84dea6..dc20549eb3 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/tools/RealmDebugTools.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/tools/RealmDebugTools.kt
@@ -25,7 +25,7 @@ internal class RealmDebugTools(
         private val realmConfiguration: RealmConfiguration
 ) {
     /**
-     * Log info about the DB
+     * Log info about the DB.
      */
     fun logInfo(baseName: String) {
         buildString {
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/di/MatrixComponent.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/di/MatrixComponent.kt
index 2fad2d8e78..dbc6aac6b5 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/di/MatrixComponent.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/di/MatrixComponent.kt
@@ -43,15 +43,17 @@ import org.matrix.android.sdk.internal.worker.MatrixWorkerFactory
 import org.matrix.olm.OlmManager
 import java.io.File
 
-@Component(modules = [
-    MatrixModule::class,
-    NetworkModule::class,
-    AuthModule::class,
-    RawModule::class,
-    SettingsModule::class,
-    SystemModule::class,
-    NoOpTestModule::class
-])
+@Component(
+        modules = [
+            MatrixModule::class,
+            NetworkModule::class,
+            AuthModule::class,
+            RawModule::class,
+            SettingsModule::class,
+            SystemModule::class,
+            NoOpTestModule::class
+        ]
+)
 @MatrixScope
 internal interface MatrixComponent {
 
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/di/MatrixModule.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/di/MatrixModule.kt
index 9cab307c61..49713a1d7f 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/di/MatrixModule.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/di/MatrixModule.kt
@@ -36,7 +36,8 @@ internal object MatrixModule {
     @Provides
     @MatrixScope
     fun providesMatrixCoroutineDispatchers(): MatrixCoroutineDispatchers {
-        return MatrixCoroutineDispatchers(io = Dispatchers.IO,
+        return MatrixCoroutineDispatchers(
+                io = Dispatchers.IO,
                 computation = Dispatchers.Default,
                 main = Dispatchers.Main,
                 crypto = createBackgroundHandler("Crypto_Thread").asCoroutineDispatcher(),
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/di/MatrixScope.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/di/MatrixScope.kt
index b027d47144..21e324c05f 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/di/MatrixScope.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/di/MatrixScope.kt
@@ -19,7 +19,7 @@ package org.matrix.android.sdk.internal.di
 import javax.inject.Scope
 
 /**
- * Use the annotation @MatrixScope to annotate classes we want the SDK to instantiate only once
+ * Use the annotation @MatrixScope to annotate classes we want the SDK to instantiate only once.
  */
 @Scope
 @MustBeDocumented
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/di/MoshiProvider.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/di/MoshiProvider.kt
index 10b0d4fb13..8f007f227c 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/di/MoshiProvider.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/di/MoshiProvider.kt
@@ -46,17 +46,18 @@ internal object MoshiProvider {
             .add(TlsVersionMoshiAdapter())
             // Use addLast here so we can inject a SplitLazyRoomSyncJsonAdapter later to override the default parsing.
             .addLast(DefaultLazyRoomSyncEphemeralJsonAdapter())
-            .add(RuntimeJsonAdapterFactory.of(MessageContent::class.java, "msgtype", MessageDefaultContent::class.java)
-                    .registerSubtype(MessageTextContent::class.java, MessageType.MSGTYPE_TEXT)
-                    .registerSubtype(MessageNoticeContent::class.java, MessageType.MSGTYPE_NOTICE)
-                    .registerSubtype(MessageEmoteContent::class.java, MessageType.MSGTYPE_EMOTE)
-                    .registerSubtype(MessageAudioContent::class.java, MessageType.MSGTYPE_AUDIO)
-                    .registerSubtype(MessageImageContent::class.java, MessageType.MSGTYPE_IMAGE)
-                    .registerSubtype(MessageVideoContent::class.java, MessageType.MSGTYPE_VIDEO)
-                    .registerSubtype(MessageLocationContent::class.java, MessageType.MSGTYPE_LOCATION)
-                    .registerSubtype(MessageFileContent::class.java, MessageType.MSGTYPE_FILE)
-                    .registerSubtype(MessageVerificationRequestContent::class.java, MessageType.MSGTYPE_VERIFICATION_REQUEST)
-                    .registerSubtype(MessagePollResponseContent::class.java, MessageType.MSGTYPE_POLL_RESPONSE)
+            .add(
+                    RuntimeJsonAdapterFactory.of(MessageContent::class.java, "msgtype", MessageDefaultContent::class.java)
+                            .registerSubtype(MessageTextContent::class.java, MessageType.MSGTYPE_TEXT)
+                            .registerSubtype(MessageNoticeContent::class.java, MessageType.MSGTYPE_NOTICE)
+                            .registerSubtype(MessageEmoteContent::class.java, MessageType.MSGTYPE_EMOTE)
+                            .registerSubtype(MessageAudioContent::class.java, MessageType.MSGTYPE_AUDIO)
+                            .registerSubtype(MessageImageContent::class.java, MessageType.MSGTYPE_IMAGE)
+                            .registerSubtype(MessageVideoContent::class.java, MessageType.MSGTYPE_VIDEO)
+                            .registerSubtype(MessageLocationContent::class.java, MessageType.MSGTYPE_LOCATION)
+                            .registerSubtype(MessageFileContent::class.java, MessageType.MSGTYPE_FILE)
+                            .registerSubtype(MessageVerificationRequestContent::class.java, MessageType.MSGTYPE_VERIFICATION_REQUEST)
+                            .registerSubtype(MessagePollResponseContent::class.java, MessageType.MSGTYPE_POLL_RESPONSE)
             )
             .add(SerializeNulls.JSON_ADAPTER_FACTORY)
             .build()
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/di/StringQualifiers.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/di/StringQualifiers.kt
index d74a8dce57..05ba6e408c 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/di/StringQualifiers.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/di/StringQualifiers.kt
@@ -19,28 +19,28 @@ package org.matrix.android.sdk.internal.di
 import javax.inject.Qualifier
 
 /**
- * Used to inject the userId
+ * Used to inject the userId.
  */
 @Qualifier
 @Retention(AnnotationRetention.RUNTIME)
 internal annotation class UserId
 
 /**
- * Used to inject the deviceId
+ * Used to inject the deviceId.
  */
 @Qualifier
 @Retention(AnnotationRetention.RUNTIME)
 internal annotation class DeviceId
 
 /**
- * Used to inject the md5 of the userId
+ * Used to inject the md5 of the userId.
  */
 @Qualifier
 @Retention(AnnotationRetention.RUNTIME)
 internal annotation class UserMd5
 
 /**
- * Used to inject the sessionId, which is defined as md5(userId|deviceId)
+ * Used to inject the sessionId, which is defined as md5(userId|deviceId).
  */
 @Qualifier
 @Retention(AnnotationRetention.RUNTIME)
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/di/WorkManagerProvider.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/di/WorkManagerProvider.kt
index fedd7d05f9..c59936af94 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/di/WorkManagerProvider.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/di/WorkManagerProvider.kt
@@ -50,14 +50,14 @@ internal class WorkManagerProvider @Inject constructor(
     }
 
     /**
-     * Create a OneTimeWorkRequestBuilder, with the Matrix SDK tag
+     * Create a OneTimeWorkRequestBuilder, with the Matrix SDK tag.
      */
     inline fun  matrixOneTimeWorkRequestBuilder() =
             OneTimeWorkRequestBuilder()
                     .addTag(tag)
 
     /**
-     * Create a PeriodicWorkRequestBuilder, with the Matrix SDK tag
+     * Create a PeriodicWorkRequestBuilder, with the Matrix SDK tag.
      */
     inline fun  matrixPeriodicWorkRequestBuilder(repeatInterval: Long,
                                                                                repeatIntervalTimeUnit: TimeUnit) =
@@ -65,7 +65,7 @@ internal class WorkManagerProvider @Inject constructor(
                     .addTag(tag)
 
     /**
-     * Cancel all works instantiated by the Matrix SDK for the current session, and not those from the SDK client, or for other sessions
+     * Cancel all works instantiated by the Matrix SDK for the current session, and not those from the SDK client, or for other sessions.
      */
     fun cancelAllWorks() {
         workManager.let {
@@ -84,9 +84,11 @@ internal class WorkManagerProvider @Inject constructor(
                     if (workInfo?.state?.isFinished == true) {
                         checkWorkerLiveState.removeObserver(this)
                         if (workInfo.state == WorkInfo.State.FAILED) {
-                            throw RuntimeException("MatrixWorkerFactory is not being set on your worker configuration.\n" +
-                                    "Makes sure to add it to a DelegatingWorkerFactory if you have your own factory or use it directly.\n" +
-                                    "You can grab the instance through the Matrix class.")
+                            throw RuntimeException(
+                                    "MatrixWorkerFactory is not being set on your worker configuration.\n" +
+                                            "Makes sure to add it to a DelegatingWorkerFactory if you have your own factory or use it directly.\n" +
+                                            "You can grab the instance through the Matrix class."
+                            )
                         }
                     }
                 }
@@ -99,7 +101,7 @@ internal class WorkManagerProvider @Inject constructor(
         private const val MATRIX_SDK_TAG_PREFIX = "MatrixSDK-"
 
         /**
-         * Default constraints: connected network
+         * Default constraints: connected network.
          */
         val workConstraints = Constraints.Builder()
                 .setRequiredNetworkType(NetworkType.CONNECTED)
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/extensions/Primitives.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/extensions/Primitives.kt
index 290f06142c..c08dfa02e8 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/extensions/Primitives.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/extensions/Primitives.kt
@@ -17,6 +17,6 @@
 package org.matrix.android.sdk.internal.extensions
 
 /**
- * Convert a signed byte to a int value
+ * Convert a signed byte to a int value.
  */
 internal fun Byte.toUnsignedInt() = toInt() and 0xff
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/extensions/RealmExtensions.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/extensions/RealmExtensions.kt
index 28b9f64188..00cbe0aa85 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/extensions/RealmExtensions.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/extensions/RealmExtensions.kt
@@ -26,7 +26,7 @@ internal fun RealmObject.assertIsManaged() {
 }
 
 /**
- * Clear a RealmList by deleting all its items calling the provided lambda
+ * Clear a RealmList by deleting all its items calling the provided lambda.
  */
 internal fun  RealmList.clearWith(delete: (T) -> Unit) {
     while (!isEmpty()) {
@@ -35,7 +35,7 @@ internal fun  RealmList.clearWith(delete: (T) -> Unit) {
 }
 
 /**
- * Schedule a refresh of the HomeServers capabilities
+ * Schedule a refresh of the HomeServers capabilities.
  */
 internal fun RealmObjectSchema?.forceRefreshOfHomeServerCapabilities(): RealmObjectSchema? {
     return this?.transform { obj ->
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/legacy/riot/WellKnown.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/legacy/riot/WellKnown.kt
index 3b4bd1b1a3..a754a0da96 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/legacy/riot/WellKnown.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/legacy/riot/WellKnown.kt
@@ -63,7 +63,7 @@ class WellKnown {
     var integrations: Map? = null
 
     /**
-     * Returns the list of integration managers proposed
+     * Returns the list of integration managers proposed.
      */
     fun getIntegrationManagers(): List {
         val managers = ArrayList()
@@ -76,10 +76,12 @@ class WellKnown {
                         if (apiUrl != null &&
                                 apiUrl.startsWith("https://") &&
                                 uiUrl!!.startsWith("https://")) {
-                            managers.add(WellKnownManagerConfig(
-                                    apiUrl = apiUrl,
-                                    uiUrl = uiUrl
-                            ))
+                            managers.add(
+                                    WellKnownManagerConfig(
+                                            apiUrl = apiUrl,
+                                            uiUrl = uiUrl
+                                    )
+                            )
                         }
                     }
                 }
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/network/NetworkConnectivityChecker.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/network/NetworkConnectivityChecker.kt
index cd7c99b8f9..65bf3fcadf 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/network/NetworkConnectivityChecker.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/network/NetworkConnectivityChecker.kt
@@ -27,7 +27,7 @@ import javax.inject.Inject
 
 internal interface NetworkConnectivityChecker {
     /**
-     * Returns true when internet is available
+     * Returns true when internet is available.
      */
     @WorkerThread
     fun hasInternetAccess(forcePing: Boolean): Boolean
@@ -44,7 +44,7 @@ internal interface NetworkConnectivityChecker {
 internal class DefaultNetworkConnectivityChecker @Inject constructor(private val homeServerPinger: HomeServerPinger,
                                                                      private val backgroundDetectionObserver: BackgroundDetectionObserver,
                                                                      private val networkCallbackStrategy: NetworkCallbackStrategy) :
-    NetworkConnectivityChecker {
+        NetworkConnectivityChecker {
 
     private val hasInternetAccess = AtomicBoolean(true)
     private val listeners = Collections.synchronizedSet(LinkedHashSet())
@@ -59,7 +59,7 @@ internal class DefaultNetworkConnectivityChecker @Inject constructor(private val
     }
 
     /**
-     * Returns true when internet is available
+     * Returns true when internet is available.
      */
     @WorkerThread
     override fun hasInternetAccess(forcePing: Boolean): Boolean {
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/network/RequestExecutor.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/network/RequestExecutor.kt
index 5cd2d88000..71df7c08be 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/network/RequestExecutor.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/network/RequestExecutor.kt
@@ -15,6 +15,7 @@
  */
 
 package org.matrix.android.sdk.internal.network
+
 import org.matrix.android.sdk.internal.network.executeRequest as internalExecuteRequest
 
 internal interface RequestExecutor {
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/network/RetrofitExtensions.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/network/RetrofitExtensions.kt
index 60055be9ec..5268ea851d 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/network/RetrofitExtensions.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/network/RetrofitExtensions.kt
@@ -53,21 +53,21 @@ internal suspend fun okhttp3.Call.awaitResponse(): okhttp3.Response {
 }
 
 /**
- * Convert a retrofit Response to a Failure, and eventually parse errorBody to convert it to a MatrixError
+ * Convert a retrofit Response to a Failure, and eventually parse errorBody to convert it to a [MatrixError].
  */
 internal fun  Response.toFailure(globalErrorReceiver: GlobalErrorReceiver?): Failure {
     return toFailure(errorBody(), code(), globalErrorReceiver)
 }
 
 /**
- * Convert a HttpException to a Failure, and eventually parse errorBody to convert it to a MatrixError
+ * Convert a HttpException to a Failure, and eventually parse errorBody to convert it to a [MatrixError].
  */
 internal fun HttpException.toFailure(globalErrorReceiver: GlobalErrorReceiver?): Failure {
     return toFailure(response()?.errorBody(), code(), globalErrorReceiver)
 }
 
 /**
- * Convert a okhttp3 Response to a Failure, and eventually parse errorBody to convert it to a MatrixError
+ * Convert a okhttp3 Response to a Failure, and eventually parse errorBody to convert it to a [MatrixError].
  */
 internal fun okhttp3.Response.toFailure(globalErrorReceiver: GlobalErrorReceiver?): Failure {
     return toFailure(body, code, globalErrorReceiver)
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/network/RetrofitFactory.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/network/RetrofitFactory.kt
index 0a797c8bc0..b2eea84b07 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/network/RetrofitFactory.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/network/RetrofitFactory.kt
@@ -29,7 +29,7 @@ import javax.inject.Inject
 internal class RetrofitFactory @Inject constructor(private val moshi: Moshi) {
 
     /**
-     * Use only for authentication service
+     * Use only for authentication service.
      */
     fun create(okHttpClient: OkHttpClient, baseUrl: String): Retrofit {
         return Retrofit.Builder()
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/network/parsing/CheckNumberType.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/network/parsing/CheckNumberType.kt
index 6efa347d3a..8b54978279 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/network/parsing/CheckNumberType.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/network/parsing/CheckNumberType.kt
@@ -33,7 +33,7 @@ internal interface CheckNumberType {
     companion object {
         val JSON_ADAPTER_FACTORY = object : JsonAdapter.Factory {
             @Nullable
-            override fun create(type: Type, annotations: Set?, moshi: Moshi): JsonAdapter<*>? {
+            override fun create(type: Type, annotations: Set, moshi: Moshi): JsonAdapter<*>? {
                 if (type !== Any::class.java) {
                     return null
                 }
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/network/parsing/RuntimeJsonAdapterFactory.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/network/parsing/RuntimeJsonAdapterFactory.kt
index 0aaa4991cd..40d174ee2d 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/network/parsing/RuntimeJsonAdapterFactory.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/network/parsing/RuntimeJsonAdapterFactory.kt
@@ -63,8 +63,10 @@ internal class RuntimeJsonAdapterFactory(
         }
         val fallbackAdapter = moshi.adapter(fallbackType)
         val objectJsonAdapter = moshi.adapter(Any::class.java)
-        return RuntimeJsonAdapter(labelKey, labelToAdapter, typeToLabel,
-                objectJsonAdapter, fallbackAdapter).nullSafe()
+        return RuntimeJsonAdapter(
+                labelKey, labelToAdapter, typeToLabel,
+                objectJsonAdapter, fallbackAdapter
+        ).nullSafe()
     }
 
     @Suppress("UNCHECKED_CAST")
@@ -77,8 +79,10 @@ internal class RuntimeJsonAdapterFactory(
         override fun fromJson(reader: JsonReader): Any? {
             val peekedToken = reader.peek()
             if (peekedToken != JsonReader.Token.BEGIN_OBJECT) {
-                throw JsonDataException("Expected BEGIN_OBJECT but was " + peekedToken +
-                        " at path " + reader.path)
+                throw JsonDataException(
+                        "Expected BEGIN_OBJECT but was " + peekedToken +
+                                " at path " + reader.path
+                )
             }
             val jsonValue = reader.readJsonValue()
             val jsonObject = jsonValue as Map?
@@ -91,13 +95,15 @@ internal class RuntimeJsonAdapterFactory(
         override fun toJson(writer: JsonWriter, value: Any?) {
             val type: Class<*> = value!!.javaClass
             val label = typeToLabel[type]
-                    ?: throw IllegalArgumentException("Expected one of " +
-                            typeToLabel.keys +
-                            " but found " +
-                            value +
-                            ", a " +
-                            value.javaClass +
-                            ". Register this subtype.")
+                    ?: throw IllegalArgumentException(
+                            "Expected one of " +
+                                    typeToLabel.keys +
+                                    " but found " +
+                                    value +
+                                    ", a " +
+                                    value.javaClass +
+                                    ". Register this subtype."
+                    )
             val adapter = labelToAdapter[label]!!
             val jsonValue = adapter.toJsonValue(value) as Map?
             val valueWithLabel: MutableMap = LinkedHashMap(1 + jsonValue!!.size)
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/network/ssl/CertUtil.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/network/ssl/CertUtil.kt
index d8bdc5fc2b..2ef40fe2a3 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/network/ssl/CertUtil.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/network/ssl/CertUtil.kt
@@ -33,7 +33,7 @@ import javax.net.ssl.TrustManagerFactory
 import javax.net.ssl.X509TrustManager
 
 /**
- * Various utility classes for dealing with X509Certificates
+ * Various utility classes for dealing with X509Certificates.
  */
 internal object CertUtil {
 
@@ -43,7 +43,7 @@ internal object CertUtil {
     private val hexArray = "0123456789ABCDEF".toCharArray()
 
     /**
-     * Generates the SHA-256 fingerprint of the given certificate
+     * Generates the SHA-256 fingerprint of the given certificate.
      *
      * @param cert the certificate.
      * @return the finger print
@@ -55,7 +55,7 @@ internal object CertUtil {
     }
 
     /**
-     * Generates the SHA-1 fingerprint of the given certificate
+     * Generates the SHA-1 fingerprint of the given certificate.
      *
      * @param cert the certificated
      * @return the SHA1 fingerprint
@@ -109,7 +109,7 @@ internal object CertUtil {
 
     /**
      * Recursively checks the exception to see if it was caused by an
-     * UnrecognizedCertificateException
+     * UnrecognizedCertificateException.
      *
      * @param root the throwable.
      * @return The UnrecognizedCertificateException if exists, else null.
@@ -250,7 +250,7 @@ internal object CertUtil {
         builder.supportsTlsExtensions(hsConfig.shouldAcceptTlsExtensions)
         val list = ArrayList()
         list.add(builder.build())
-        // TODO: we should display a warning if user enter an http url
+        // TODO we should display a warning if user enter an http url
         if (hsConfig.allowHttpExtension || hsConfig.homeServerUriBase.toString().startsWith("http://")) {
             list.add(ConnectionSpec.CLEARTEXT)
         }
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/query/QueryRoomOrderProcessor.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/query/QueryRoomOrderProcessor.kt
index 7294ce4abf..c5ea2d48ad 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/query/QueryRoomOrderProcessor.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/query/QueryRoomOrderProcessor.kt
@@ -35,8 +35,10 @@ internal fun RealmQuery.process(sortOrder: RoomSortOrder): Re
                     arrayOf(
                             RoomSummaryEntityFields.IS_FAVOURITE,
                             RoomSummaryEntityFields.IS_LOW_PRIORITY,
-                            RoomSummaryEntityFields.LAST_ACTIVITY_TIME),
-                    arrayOf(Sort.DESCENDING, Sort.ASCENDING, Sort.DESCENDING))
+                            RoomSummaryEntityFields.LAST_ACTIVITY_TIME
+                    ),
+                    arrayOf(Sort.DESCENDING, Sort.ASCENDING, Sort.DESCENDING)
+            )
         }
         RoomSortOrder.NONE                  -> {
         }
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/raw/GlobalRealmMigration.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/raw/GlobalRealmMigration.kt
index 8dffac5fa0..a9dfd47b5a 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/raw/GlobalRealmMigration.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/raw/GlobalRealmMigration.kt
@@ -24,8 +24,8 @@ import javax.inject.Inject
 
 internal class GlobalRealmMigration @Inject constructor() : RealmMigration {
     /**
-     * Forces all GlobalRealmMigration instances to be equal
-     * Avoids Realm throwing when multiple instances of the migration are set
+     * Forces all GlobalRealmMigration instances to be equal.
+     * Avoids Realm throwing when multiple instances of the migration are set.
      */
     override fun equals(other: Any?) = other is GlobalRealmMigration
     override fun hashCode() = 2000
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/raw/GlobalRealmModule.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/raw/GlobalRealmModule.kt
index 770a49c904..2d9ec8e820 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/raw/GlobalRealmModule.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/raw/GlobalRealmModule.kt
@@ -21,11 +21,13 @@ import org.matrix.android.sdk.internal.database.model.KnownServerUrlEntity
 import org.matrix.android.sdk.internal.database.model.RawCacheEntity
 
 /**
- * Realm module for global classes
+ * Realm module for global classes.
  */
-@RealmModule(library = true,
+@RealmModule(
+        library = true,
         classes = [
             RawCacheEntity::class,
             KnownServerUrlEntity::class
-        ])
+        ]
+)
 internal class GlobalRealmModule
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/DefaultFileService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/DefaultFileService.kt
index ac097f57ee..760aa41081 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/DefaultFileService.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/DefaultFileService.kt
@@ -38,6 +38,7 @@ import org.matrix.android.sdk.internal.di.SessionDownloadsDirectory
 import org.matrix.android.sdk.internal.di.UnauthenticatedWithCertificateWithProgress
 import org.matrix.android.sdk.internal.session.download.DownloadProgressInterceptor.Companion.DOWNLOAD_PROGRESS_INTERCEPTOR_HEADER
 import org.matrix.android.sdk.internal.util.file.AtomicFileCreator
+import org.matrix.android.sdk.internal.util.time.Clock
 import org.matrix.android.sdk.internal.util.writeToFile
 import timber.log.Timber
 import java.io.File
@@ -51,7 +52,8 @@ internal class DefaultFileService @Inject constructor(
         private val contentUrlResolver: ContentUrlResolver,
         @UnauthenticatedWithCertificateWithProgress
         private val okHttpClient: OkHttpClient,
-        private val coroutineDispatchers: MatrixCoroutineDispatchers
+        private val coroutineDispatchers: MatrixCoroutineDispatchers,
+        private val clock: Clock,
 ) : FileService {
 
     // Legacy folder, will be deleted
@@ -70,12 +72,12 @@ internal class DefaultFileService @Inject constructor(
 
     /**
      * Retain ongoing downloads to avoid re-downloading and already downloading file
-     * map of mxCurl to callbacks
+     * map of mxCurl to callbacks.
      */
     private val ongoing = mutableMapOf>()
 
     /**
-     * Download file in the cache folder, and eventually decrypt it
+     * Download file in the cache folder, and eventually decrypt it.
      * TODO looks like files are copied 3 times
      */
     override suspend fun downloadFile(fileName: String,
@@ -86,7 +88,7 @@ internal class DefaultFileService @Inject constructor(
 
         Timber.v("## FileService downloadFile $url")
 
-        // TODO: Remove use of `synchronized` in suspend function.
+        // TODO Remove use of `synchronized` in suspend function.
         val existingDownload = synchronized(ongoing) {
             val existing = ongoing[url]
             if (existing != null) {
@@ -123,7 +125,7 @@ internal class DefaultFileService @Inject constructor(
                     val resolvedUrl = contentUrlResolver.resolveForDownload(url, elementToDecrypt) ?: throw IllegalArgumentException("url is null")
 
                     val request = when (resolvedUrl) {
-                        is ContentUrlResolver.ResolvedMethod.GET -> {
+                        is ContentUrlResolver.ResolvedMethod.GET  -> {
                             Request.Builder()
                                     .url(resolvedUrl.url)
                                     .header(DOWNLOAD_PROGRESS_INTERCEPTOR_HEADER, url)
@@ -182,7 +184,8 @@ internal class DefaultFileService @Inject constructor(
                             MXEncryptedAttachments.decryptAttachment(
                                     inputStream,
                                     elementToDecrypt,
-                                    outputStream
+                                    outputStream,
+                                    clock
                             )
                         }
                     }
@@ -309,7 +312,7 @@ internal class DefaultFileService @Inject constructor(
 
     /**
      * Use this URI and pass it to intent using flag Intent.FLAG_GRANT_READ_URI_PERMISSION
-     * (if not other app won't be able to access it)
+     * (if not other app won't be able to access it).
      */
     override fun getTemporarySharableURI(mxcUrl: String?,
                                          fileName: String,
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/DefaultSession.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/DefaultSession.kt
index 1e533158a7..5f77cfb23a 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/DefaultSession.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/DefaultSession.kt
@@ -26,7 +26,6 @@ import org.matrix.android.sdk.api.MatrixCoroutineDispatchers
 import org.matrix.android.sdk.api.auth.data.SessionParams
 import org.matrix.android.sdk.api.failure.GlobalError
 import org.matrix.android.sdk.api.federation.FederationService
-import org.matrix.android.sdk.api.pushrules.PushRuleService
 import org.matrix.android.sdk.api.session.EventStreamService
 import org.matrix.android.sdk.api.session.Session
 import org.matrix.android.sdk.api.session.SessionLifecycleObserver
@@ -53,6 +52,7 @@ import org.matrix.android.sdk.api.session.permalinks.PermalinkService
 import org.matrix.android.sdk.api.session.presence.PresenceService
 import org.matrix.android.sdk.api.session.profile.ProfileService
 import org.matrix.android.sdk.api.session.pushers.PushersService
+import org.matrix.android.sdk.api.session.pushrules.PushRuleService
 import org.matrix.android.sdk.api.session.room.RoomDirectoryService
 import org.matrix.android.sdk.api.session.room.RoomService
 import org.matrix.android.sdk.api.session.search.SearchService
@@ -124,12 +124,12 @@ internal class DefaultSession @Inject constructor(
         private val syncStatusService: Lazy,
         private val homeServerCapabilitiesService: Lazy,
         private val accountDataService: Lazy,
-        private val _sharedSecretStorageService: Lazy,
+        private val sharedSecretStorageService: Lazy,
         private val accountService: Lazy,
         private val eventService: Lazy,
         private val contentScannerService: Lazy,
-        private val identityService: IdentityService,
-        private val integrationManagerService: IntegrationManagerService,
+        private val identityService: Lazy,
+        private val integrationManagerService: Lazy,
         private val thirdPartyService: Lazy,
         private val callSignalingService: Lazy,
         private val spaceService: Lazy,
@@ -140,28 +140,7 @@ internal class DefaultSession @Inject constructor(
         @UnauthenticatedWithCertificate
         private val unauthenticatedWithCertificateOkHttpClient: Lazy
 ) : Session,
-        GlobalErrorHandler.Listener,
-        RoomService by roomService.get(),
-        RoomDirectoryService by roomDirectoryService.get(),
-        GroupService by groupService.get(),
-        UserService by userService.get(),
-        SignOutService by signOutService.get(),
-        FilterService by filterService.get(),
-        PushRuleService by pushRuleService.get(),
-        PushersService by pushersService.get(),
-        EventService by eventService.get(),
-        TermsService by termsService.get(),
-        SyncStatusService by syncStatusService.get(),
-        SecureStorageService by secureStorageService.get(),
-        HomeServerCapabilitiesService by homeServerCapabilitiesService.get(),
-        ProfileService by profileService.get(),
-        PresenceService by presenceService.get(),
-        AccountService by accountService.get(),
-        ToDeviceService by toDeviceService.get(),
-        EventStreamService by eventStreamService.get() {
-
-    override val sharedSecretStorageService: SharedSecretStorageService
-        get() = _sharedSecretStorageService.get()
+        GlobalErrorHandler.Listener {
 
     private var isOpen = false
 
@@ -274,42 +253,44 @@ internal class DefaultSession @Inject constructor(
     }
 
     override fun contentUrlResolver() = contentUrlResolver
-
     override fun contentUploadProgressTracker() = contentUploadProgressTracker
-
     override fun typingUsersTracker() = typingUsersTracker
-
     override fun contentDownloadProgressTracker(): ContentDownloadStateTracker = contentDownloadStateTracker
 
     override fun cryptoService(): CryptoService = cryptoService.get()
-
     override fun contentScannerService(): ContentScannerService = contentScannerService.get()
-
-    override fun identityService() = identityService
-
+    override fun identityService(): IdentityService = identityService.get()
+    override fun homeServerCapabilitiesService(): HomeServerCapabilitiesService = homeServerCapabilitiesService.get()
+    override fun roomService(): RoomService = roomService.get()
+    override fun roomDirectoryService(): RoomDirectoryService = roomDirectoryService.get()
+    override fun groupService(): GroupService = groupService.get()
+    override fun userService(): UserService = userService.get()
+    override fun signOutService(): SignOutService = signOutService.get()
+    override fun filterService(): FilterService = filterService.get()
+    override fun pushRuleService(): PushRuleService = pushRuleService.get()
+    override fun pushersService(): PushersService = pushersService.get()
+    override fun eventService(): EventService = eventService.get()
+    override fun termsService(): TermsService = termsService.get()
+    override fun syncStatusService(): SyncStatusService = syncStatusService.get()
+    override fun secureStorageService(): SecureStorageService = secureStorageService.get()
+    override fun profileService(): ProfileService = profileService.get()
+    override fun presenceService(): PresenceService = presenceService.get()
+    override fun accountService(): AccountService = accountService.get()
+    override fun toDeviceService(): ToDeviceService = toDeviceService.get()
+    override fun eventStreamService(): EventStreamService = eventStreamService.get()
     override fun fileService(): FileService = defaultFileService.get()
-
     override fun permalinkService(): PermalinkService = permalinkService.get()
-
     override fun widgetService(): WidgetService = widgetService.get()
-
     override fun mediaService(): MediaService = mediaService.get()
-
-    override fun integrationManagerService() = integrationManagerService
-
+    override fun integrationManagerService(): IntegrationManagerService = integrationManagerService.get()
     override fun callSignalingService(): CallSignalingService = callSignalingService.get()
-
     override fun searchService(): SearchService = searchService.get()
-
     override fun federationService(): FederationService = federationService.get()
-
     override fun thirdPartyService(): ThirdPartyService = thirdPartyService.get()
-
     override fun spaceService(): SpaceService = spaceService.get()
-
     override fun openIdService(): OpenIdService = openIdService.get()
-
     override fun accountDataService(): SessionAccountDataService = accountDataService.get()
+    override fun sharedSecretStorageService(): SharedSecretStorageService = sharedSecretStorageService.get()
 
     override fun getOkHttpClient(): OkHttpClient {
         return unauthenticatedWithCertificateOkHttpClient.get()
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/SessionComponent.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/SessionComponent.kt
index 76e5d84e56..050480e6c9 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/SessionComponent.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/SessionComponent.kt
@@ -21,12 +21,8 @@ import dagger.Component
 import org.matrix.android.sdk.api.MatrixCoroutineDispatchers
 import org.matrix.android.sdk.api.auth.data.SessionParams
 import org.matrix.android.sdk.api.session.Session
-import org.matrix.android.sdk.internal.crypto.CancelGossipRequestWorker
 import org.matrix.android.sdk.internal.crypto.CryptoModule
-import org.matrix.android.sdk.internal.crypto.SendGossipRequestWorker
-import org.matrix.android.sdk.internal.crypto.SendGossipWorker
 import org.matrix.android.sdk.internal.crypto.crosssigning.UpdateTrustWorker
-import org.matrix.android.sdk.internal.crypto.verification.SendVerificationMessageWorker
 import org.matrix.android.sdk.internal.di.MatrixComponent
 import org.matrix.android.sdk.internal.federation.FederationModule
 import org.matrix.android.sdk.internal.network.NetworkConnectivityChecker
@@ -68,7 +64,8 @@ import org.matrix.android.sdk.internal.session.widgets.WidgetModule
 import org.matrix.android.sdk.internal.task.TaskExecutor
 import org.matrix.android.sdk.internal.util.system.SystemModule
 
-@Component(dependencies = [MatrixComponent::class],
+@Component(
+        dependencies = [MatrixComponent::class],
         modules = [
             SessionModule::class,
             RoomModule::class,
@@ -132,14 +129,6 @@ internal interface SessionComponent {
 
     fun inject(worker: AddPusherWorker)
 
-    fun inject(worker: SendVerificationMessageWorker)
-
-    fun inject(worker: SendGossipRequestWorker)
-
-    fun inject(worker: CancelGossipRequestWorker)
-
-    fun inject(worker: SendGossipWorker)
-
     fun inject(worker: UpdateTrustWorker)
 
     @Component.Factory
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/SessionModule.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/SessionModule.kt
index 0aae9f3105..7ceb89e892 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/SessionModule.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/SessionModule.kt
@@ -49,7 +49,6 @@ import org.matrix.android.sdk.api.util.md5
 import org.matrix.android.sdk.internal.crypto.secrets.DefaultSharedSecretStorageService
 import org.matrix.android.sdk.internal.crypto.tasks.DefaultRedactEventTask
 import org.matrix.android.sdk.internal.crypto.tasks.RedactEventTask
-import org.matrix.android.sdk.internal.crypto.verification.VerificationMessageProcessor
 import org.matrix.android.sdk.internal.database.EventInsertLiveObserver
 import org.matrix.android.sdk.internal.database.RealmSessionProvider
 import org.matrix.android.sdk.internal.database.SessionRealmConfigurationFactory
@@ -318,10 +317,6 @@ internal abstract class SessionModule {
     @IntoSet
     abstract fun bindRoomCreateEventProcessor(processor: RoomCreateEventProcessor): EventInsertLiveProcessor
 
-    @Binds
-    @IntoSet
-    abstract fun bindVerificationMessageProcessor(processor: VerificationMessageProcessor): EventInsertLiveProcessor
-
     @Binds
     @IntoSet
     abstract fun bindCallEventProcessor(processor: CallEventProcessor): EventInsertLiveProcessor
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/account/AccountAPI.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/account/AccountAPI.kt
index a04d0f2686..4bd3b6360d 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/account/AccountAPI.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/account/AccountAPI.kt
@@ -30,7 +30,7 @@ internal interface AccountAPI {
     suspend fun changePassword(@Body params: ChangePasswordParams)
 
     /**
-     * Deactivate the user account
+     * Deactivate the user account.
      *
      * @param params the deactivate account params
      */
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/account/DeactivateAccountTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/account/DeactivateAccountTask.kt
index 752856b931..9f3f1f649e 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/account/DeactivateAccountTask.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/account/DeactivateAccountTask.kt
@@ -18,6 +18,8 @@ package org.matrix.android.sdk.internal.session.account
 
 import org.matrix.android.sdk.api.auth.UIABaseAuth
 import org.matrix.android.sdk.api.auth.UserInteractiveAuthInterceptor
+import org.matrix.android.sdk.api.session.uia.UiaResult
+import org.matrix.android.sdk.api.session.uia.exceptions.UiaCancelledException
 import org.matrix.android.sdk.internal.auth.registration.handleUIA
 import org.matrix.android.sdk.internal.network.GlobalErrorReceiver
 import org.matrix.android.sdk.internal.network.executeRequest
@@ -51,18 +53,24 @@ internal class DefaultDeactivateAccountTask @Inject constructor(
             }
             true
         } catch (throwable: Throwable) {
-            if (!handleUIA(
-                            failure = throwable,
-                            interceptor = params.userInteractiveAuthInterceptor,
-                            retryBlock = { authUpdate ->
-                                execute(params.copy(userAuthParam = authUpdate))
-                            }
-                    )
-            ) {
-                Timber.d("## UIA: propagate failure")
-                throw throwable
-            } else {
-                false
+            when (handleUIA(
+                    failure = throwable,
+                    interceptor = params.userInteractiveAuthInterceptor,
+                    retryBlock = { authUpdate ->
+                        execute(params.copy(userAuthParam = authUpdate))
+                    }
+            )) {
+                UiaResult.SUCCESS   -> {
+                    false
+                }
+                UiaResult.FAILURE   -> {
+                    Timber.d("## UIA: propagate failure")
+                    throw throwable
+                }
+                UiaResult.CANCELLED -> {
+                    Timber.d("## UIA: cancelled")
+                    throw UiaCancelledException()
+                }
             }
         }
 
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/call/CallEventProcessor.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/call/CallEventProcessor.kt
index 3f199c5cce..b15a647421 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/call/CallEventProcessor.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/call/CallEventProcessor.kt
@@ -30,7 +30,7 @@ private val loggerTag = LoggerTag("CallEventProcessor", LoggerTag.VOIP)
 
 @SessionScope
 internal class CallEventProcessor @Inject constructor(private val callSignalingHandler: CallSignalingHandler) :
-    EventInsertLiveProcessor {
+        EventInsertLiveProcessor {
 
     private val allowedTypes = listOf(
             EventType.CALL_ANSWER,
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/call/CallSignalingHandler.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/call/CallSignalingHandler.kt
index 59058bf976..c4f711a9e6 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/call/CallSignalingHandler.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/call/CallSignalingHandler.kt
@@ -34,6 +34,7 @@ import org.matrix.android.sdk.api.session.room.model.call.CallSelectAnswerConten
 import org.matrix.android.sdk.api.session.room.model.call.CallSignalingContent
 import org.matrix.android.sdk.internal.di.UserId
 import org.matrix.android.sdk.internal.session.SessionScope
+import org.matrix.android.sdk.internal.util.time.Clock
 import timber.log.Timber
 import javax.inject.Inject
 
@@ -41,9 +42,12 @@ private val loggerTag = LoggerTag("CallSignalingHandler", LoggerTag.VOIP)
 private const val MAX_AGE_TO_RING = 40_000
 
 @SessionScope
-internal class CallSignalingHandler @Inject constructor(private val activeCallHandler: ActiveCallHandler,
-                                                        private val mxCallFactory: MxCallFactory,
-                                                        @UserId private val userId: String) {
+internal class CallSignalingHandler @Inject constructor(
+        private val activeCallHandler: ActiveCallHandler,
+        private val mxCallFactory: MxCallFactory,
+        @UserId private val userId: String,
+        private val clock: Clock,
+) {
 
     private val invitedCallIds = mutableSetOf()
     private val callListeners = mutableSetOf()
@@ -184,7 +188,7 @@ internal class CallSignalingHandler @Inject constructor(private val activeCallHa
         if (event.roomId == null || event.senderId == null) {
             return
         }
-        val now = System.currentTimeMillis()
+        val now = clock.epochMillis()
         val age = now - (event.ageLocalTs ?: now)
         if (age > MAX_AGE_TO_RING) {
             Timber.tag(loggerTag.value).w("Call invite is too old to ring.")
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/call/GetTurnServerTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/call/GetTurnServerTask.kt
index d53ddb7371..1313fcaa62 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/call/GetTurnServerTask.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/call/GetTurnServerTask.kt
@@ -22,16 +22,16 @@ import org.matrix.android.sdk.internal.network.executeRequest
 import org.matrix.android.sdk.internal.task.Task
 import javax.inject.Inject
 
-internal abstract class GetTurnServerTask : Task {
-    object Params
-}
+internal abstract class GetTurnServerTask : Task
 
-internal class DefaultGetTurnServerTask @Inject constructor(private val voipAPI: VoipApi,
-                                                            private val globalErrorReceiver: GlobalErrorReceiver) : GetTurnServerTask() {
+internal class DefaultGetTurnServerTask @Inject constructor(
+        private val voipApi: VoipApi,
+        private val globalErrorReceiver: GlobalErrorReceiver
+) : GetTurnServerTask() {
 
-    override suspend fun execute(params: Params): TurnServerResponse {
+    override suspend fun execute(params: Unit): TurnServerResponse {
         return executeRequest(globalErrorReceiver) {
-            voipAPI.getTurnServer()
+            voipApi.getTurnServer()
         }
     }
 }
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/call/MxCallFactory.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/call/MxCallFactory.kt
index 547be2253f..9ec892b65d 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/call/MxCallFactory.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/call/MxCallFactory.kt
@@ -28,6 +28,7 @@ import org.matrix.android.sdk.internal.session.call.model.MxCallImpl
 import org.matrix.android.sdk.internal.session.profile.GetProfileInfoTask
 import org.matrix.android.sdk.internal.session.room.send.LocalEchoEventFactory
 import org.matrix.android.sdk.internal.session.room.send.queue.EventSenderProcessor
+import org.matrix.android.sdk.internal.util.time.Clock
 import javax.inject.Inject
 
 internal class MxCallFactory @Inject constructor(
@@ -36,7 +37,8 @@ internal class MxCallFactory @Inject constructor(
         private val eventSenderProcessor: EventSenderProcessor,
         private val matrixConfiguration: MatrixConfiguration,
         private val getProfileInfoTask: GetProfileInfoTask,
-        @UserId private val userId: String
+        @UserId private val userId: String,
+        private val clock: Clock,
 ) {
 
     fun createIncomingCall(roomId: String, opponentUserId: String, content: CallInviteContent): MxCall? {
@@ -51,7 +53,8 @@ internal class MxCallFactory @Inject constructor(
                 localEchoEventFactory = localEchoEventFactory,
                 eventSenderProcessor = eventSenderProcessor,
                 matrixConfiguration = matrixConfiguration,
-                getProfileInfoTask = getProfileInfoTask
+                getProfileInfoTask = getProfileInfoTask,
+                clock = clock,
         ).apply {
             updateOpponentData(opponentUserId, content, content.capabilities)
         }
@@ -68,7 +71,8 @@ internal class MxCallFactory @Inject constructor(
                 localEchoEventFactory = localEchoEventFactory,
                 eventSenderProcessor = eventSenderProcessor,
                 matrixConfiguration = matrixConfiguration,
-                getProfileInfoTask = getProfileInfoTask
+                getProfileInfoTask = getProfileInfoTask,
+                clock = clock,
         ).apply {
             // Setup with this userId, might be updated when processing the Answer event
             this.opponentUserId = opponentUserId
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/call/TurnServerDataSource.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/call/TurnServerDataSource.kt
index 8e2ac5e17e..126a581fa4 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/call/TurnServerDataSource.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/call/TurnServerDataSource.kt
@@ -39,7 +39,7 @@ internal class TurnServerDataSource @Inject constructor(private val turnServerTa
     }
 
     suspend fun getTurnServer(): TurnServerResponse {
-        return cachedTurnServerResponse.data ?: turnServerTask.execute(GetTurnServerTask.Params).also {
+        return cachedTurnServerResponse.data ?: turnServerTask.execute(Unit).also {
             cachedTurnServerResponse.data = it
         }
     }
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/call/model/MxCallImpl.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/call/model/MxCallImpl.kt
index a89713870a..796e83311f 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/call/model/MxCallImpl.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/call/model/MxCallImpl.kt
@@ -46,6 +46,7 @@ import org.matrix.android.sdk.internal.session.call.DefaultCallSignalingService
 import org.matrix.android.sdk.internal.session.profile.GetProfileInfoTask
 import org.matrix.android.sdk.internal.session.room.send.LocalEchoEventFactory
 import org.matrix.android.sdk.internal.session.room.send.queue.EventSenderProcessor
+import org.matrix.android.sdk.internal.util.time.Clock
 import timber.log.Timber
 import java.math.BigDecimal
 
@@ -61,7 +62,8 @@ internal class MxCallImpl(
         private val localEchoEventFactory: LocalEchoEventFactory,
         private val eventSenderProcessor: EventSenderProcessor,
         private val matrixConfiguration: MatrixConfiguration,
-        private val getProfileInfoTask: GetProfileInfoTask
+        private val getProfileInfoTask: GetProfileInfoTask,
+        private val clock: Clock,
 ) : MxCall {
 
     override var opponentPartyId: Optional? = null
@@ -250,7 +252,7 @@ internal class MxCallImpl(
     private fun createEventAndLocalEcho(localId: String = LocalEcho.createLocalEchoId(), type: String, roomId: String, content: Content): Event {
         return Event(
                 roomId = roomId,
-                originServerTs = System.currentTimeMillis(),
+                originServerTs = clock.epochMillis(),
                 senderId = userId,
                 eventId = localId,
                 type = type,
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/content/FileUploader.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/content/FileUploader.kt
index e9cb423893..f96a019fe2 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/content/FileUploader.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/content/FileUploader.kt
@@ -148,8 +148,8 @@ internal class FileUploader @Inject constructor(
                 .post(requestBody)
                 .build()
 
-       return withContext(coroutineDispatchers.io) {
-             okHttpClient.newCall(request).awaitResponse().use { response ->
+        return withContext(coroutineDispatchers.io) {
+            okHttpClient.newCall(request).awaitResponse().use { response ->
                 if (!response.isSuccessful) {
                     throw response.toFailure(globalErrorReceiver)
                 } else {
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/content/ImageCompressor.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/content/ImageCompressor.kt
index 01eb52ff22..c5aa6cd5e7 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/content/ImageCompressor.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/content/ImageCompressor.kt
@@ -68,16 +68,16 @@ internal class ImageCompressor @Inject constructor(
                     val orientation = exifInfo.getAttributeInt(ExifInterface.TAG_ORIENTATION, ExifInterface.ORIENTATION_NORMAL)
                     val matrix = Matrix()
                     when (orientation) {
-                        ExifInterface.ORIENTATION_ROTATE_270 -> matrix.postRotate(270f)
-                        ExifInterface.ORIENTATION_ROTATE_180 -> matrix.postRotate(180f)
-                        ExifInterface.ORIENTATION_ROTATE_90 -> matrix.postRotate(90f)
+                        ExifInterface.ORIENTATION_ROTATE_270      -> matrix.postRotate(270f)
+                        ExifInterface.ORIENTATION_ROTATE_180      -> matrix.postRotate(180f)
+                        ExifInterface.ORIENTATION_ROTATE_90       -> matrix.postRotate(90f)
                         ExifInterface.ORIENTATION_FLIP_HORIZONTAL -> matrix.preScale(-1f, 1f)
-                        ExifInterface.ORIENTATION_FLIP_VERTICAL -> matrix.preScale(1f, -1f)
-                        ExifInterface.ORIENTATION_TRANSPOSE -> {
+                        ExifInterface.ORIENTATION_FLIP_VERTICAL   -> matrix.preScale(1f, -1f)
+                        ExifInterface.ORIENTATION_TRANSPOSE       -> {
                             matrix.preRotate(-90f)
                             matrix.preScale(-1f, 1f)
                         }
-                        ExifInterface.ORIENTATION_TRANSVERSE -> {
+                        ExifInterface.ORIENTATION_TRANSVERSE      -> {
                             matrix.preRotate(90f)
                             matrix.preScale(-1f, 1f)
                         }
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/content/ImageExifTagRemover.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/content/ImageExifTagRemover.kt
index 239a768498..3fa9ffb0e1 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/content/ImageExifTagRemover.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/content/ImageExifTagRemover.kt
@@ -31,9 +31,8 @@ import java.io.FileOutputStream
 import javax.inject.Inject
 
 /**
- * This class is responsible for removing Exif tags from image files
+ * This class is responsible for removing Exif tags from image files.
  */
-
 internal class ImageExifTagRemover @Inject constructor(
         private val temporaryFileCreator: TemporaryFileCreator,
         private val coroutineDispatchers: MatrixCoroutineDispatchers
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/content/UploadContentWorker.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/content/UploadContentWorker.kt
index 75606f2e7a..5a00c4b5b4 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/content/UploadContentWorker.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/content/UploadContentWorker.kt
@@ -46,6 +46,7 @@ import org.matrix.android.sdk.internal.session.room.send.LocalEchoIdentifiers
 import org.matrix.android.sdk.internal.session.room.send.LocalEchoRepository
 import org.matrix.android.sdk.internal.session.room.send.MultipleEventSendingDispatcherWorker
 import org.matrix.android.sdk.internal.util.TemporaryFileCreator
+import org.matrix.android.sdk.internal.util.time.Clock
 import org.matrix.android.sdk.internal.util.toMatrixErrorStr
 import org.matrix.android.sdk.internal.worker.SessionSafeCoroutineWorker
 import org.matrix.android.sdk.internal.worker.SessionWorkerParams
@@ -61,8 +62,8 @@ private data class NewAttachmentAttributes(
 )
 
 /**
- * Possible previous worker: None
- * Possible next worker    : Always [MultipleEventSendingDispatcherWorker]
+ * Possible previous worker: None.
+ * Possible next worker    : Always [MultipleEventSendingDispatcherWorker].
  */
 internal class UploadContentWorker(val context: Context, params: WorkerParameters, sessionManager: SessionManager) :
         SessionSafeCoroutineWorker(context, params, sessionManager, Params::class.java) {
@@ -87,6 +88,7 @@ internal class UploadContentWorker(val context: Context, params: WorkerParameter
     @Inject lateinit var thumbnailExtractor: ThumbnailExtractor
     @Inject lateinit var localEchoRepository: LocalEchoRepository
     @Inject lateinit var temporaryFileCreator: TemporaryFileCreator
+    @Inject lateinit var clock: Clock
 
     override fun injectWith(injector: SessionComponent) {
         injector.inject(this)
@@ -243,7 +245,7 @@ internal class UploadContentWorker(val context: Context, params: WorkerParameter
                             .also { filesToDelete.add(it) }
 
                     uploadedFileEncryptedFileInfo =
-                            MXEncryptedAttachments.encrypt(fileToUpload.inputStream(), encryptedFile) { read, total ->
+                            MXEncryptedAttachments.encrypt(fileToUpload.inputStream(), encryptedFile, clock) { read, total ->
                                 notifyTracker(params) {
                                     contentUploadStateTracker.setEncrypting(it, read.toLong(), total.toLong())
                                 }
@@ -287,12 +289,14 @@ internal class UploadContentWorker(val context: Context, params: WorkerParameter
 
                 val uploadThumbnailResult = dealWithThumbnail(params)
 
-                handleSuccess(params,
+                handleSuccess(
+                        params,
                         contentUploadResponse.contentUri,
                         uploadedFileEncryptedFileInfo,
                         uploadThumbnailResult?.uploadedThumbnailUrl,
                         uploadThumbnailResult?.uploadedThumbnailEncryptedFileInfo,
-                        newAttachmentAttributes)
+                        newAttachmentAttributes
+                )
             } catch (t: Throwable) {
                 Timber.e(t, "## ERROR ${t.localizedMessage}")
                 handleFailure(params, t)
@@ -314,7 +318,7 @@ internal class UploadContentWorker(val context: Context, params: WorkerParameter
     )
 
     /**
-     * If appropriate, it will create and upload a thumbnail
+     * If appropriate, it will create and upload a thumbnail.
      */
     private suspend fun dealWithThumbnail(params: Params): UploadThumbnailResult? {
         return thumbnailExtractor.extractThumbnail(params.attachment)
@@ -329,7 +333,7 @@ internal class UploadContentWorker(val context: Context, params: WorkerParameter
                         if (params.isEncrypted) {
                             Timber.v("Encrypt thumbnail")
                             notifyTracker(params) { contentUploadStateTracker.setEncryptingThumbnail(it) }
-                            val encryptionResult = MXEncryptedAttachments.encryptAttachment(thumbnailData.bytes.inputStream())
+                            val encryptionResult = MXEncryptedAttachments.encryptAttachment(thumbnailData.bytes.inputStream(), clock)
                             val contentUploadResponse = fileUploader.uploadByteArray(
                                     byteArray = encryptionResult.encryptedByteArray,
                                     filename = null,
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/contentscanner/DefaultContentScannerService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/contentscanner/DefaultContentScannerService.kt
index da7e2d102e..5aaf058757 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/contentscanner/DefaultContentScannerService.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/contentscanner/DefaultContentScannerService.kt
@@ -33,6 +33,7 @@ import org.matrix.android.sdk.internal.session.contentscanner.tasks.GetServerPub
 import org.matrix.android.sdk.internal.session.contentscanner.tasks.ScanEncryptedTask
 import org.matrix.android.sdk.internal.session.contentscanner.tasks.ScanMediaTask
 import org.matrix.android.sdk.internal.task.TaskExecutor
+import org.matrix.android.sdk.internal.util.time.Clock
 import timber.log.Timber
 import javax.inject.Inject
 
@@ -46,7 +47,8 @@ internal class DefaultContentScannerService @Inject constructor(
         private val getServerPublicKeyTask: GetServerPublicKeyTask,
         private val scanEncryptedTask: ScanEncryptedTask,
         private val scanMediaTask: ScanMediaTask,
-        private val taskExecutor: TaskExecutor
+        private val taskExecutor: TaskExecutor,
+        private val clock: Clock,
 ) : ContentScannerService {
 
     // Cache public key in memory
@@ -71,11 +73,13 @@ internal class DefaultContentScannerService @Inject constructor(
 
     override suspend fun getScanResultForAttachment(mxcUrl: String, fileInfo: ElementToDecrypt?): ScanStatusInfo {
         val result = if (fileInfo != null) {
-            scanEncryptedTask.execute(ScanEncryptedTask.Params(
-                    mxcUrl = mxcUrl,
-                    publicServerKey = getServerPublicKey(false),
-                    encryptedInfo = fileInfo
-            ))
+            scanEncryptedTask.execute(
+                    ScanEncryptedTask.Params(
+                            mxcUrl = mxcUrl,
+                            publicServerKey = getServerPublicKey(false),
+                            encryptedInfo = fileInfo
+                    )
+            )
         } else {
             scanMediaTask.execute(ScanMediaTask.Params(mxcUrl))
         }
@@ -83,7 +87,7 @@ internal class DefaultContentScannerService @Inject constructor(
         return ScanStatusInfo(
                 state = if (result.clean) ScanState.TRUSTED else ScanState.INFECTED,
                 humanReadableMessage = result.info,
-                scanDateTimestamp = System.currentTimeMillis()
+                scanDateTimestamp = clock.epochMillis()
         )
     }
 
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/contentscanner/db/ContentScannerEntityQueries.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/contentscanner/db/ContentScannerEntityQueries.kt
index b47be235c6..e4b64a1a0e 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/contentscanner/db/ContentScannerEntityQueries.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/contentscanner/db/ContentScannerEntityQueries.kt
@@ -31,11 +31,14 @@ internal fun ContentScanResultEntity.Companion.get(realm: Realm, attachmentUrl:
             .findFirst()
 }
 
-internal fun ContentScanResultEntity.Companion.getOrCreate(realm: Realm, attachmentUrl: String, contentScannerUrl: String?): ContentScanResultEntity {
+internal fun ContentScanResultEntity.Companion.getOrCreate(realm: Realm,
+                                                           attachmentUrl: String,
+                                                           contentScannerUrl: String?,
+                                                           currentTimeMillis: Long): ContentScanResultEntity {
     return ContentScanResultEntity.get(realm, attachmentUrl, contentScannerUrl)
             ?: realm.createObject().also {
                 it.mediaUrl = attachmentUrl
-                it.scanDateTimestamp = System.currentTimeMillis()
+                it.scanDateTimestamp = currentTimeMillis
                 it.scannerUrl = contentScannerUrl
             }
 }
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/contentscanner/db/ContentScannerRealmModule.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/contentscanner/db/ContentScannerRealmModule.kt
index bb53140ad9..1872bb72a9 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/contentscanner/db/ContentScannerRealmModule.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/contentscanner/db/ContentScannerRealmModule.kt
@@ -19,11 +19,13 @@ package org.matrix.android.sdk.internal.session.contentscanner.db
 import io.realm.annotations.RealmModule
 
 /**
- * Realm module for content scanner classes
+ * Realm module for content scanner classes.
  */
-@RealmModule(library = true,
+@RealmModule(
+        library = true,
         classes = [
             ContentScannerInfoEntity::class,
             ContentScanResultEntity::class
-        ])
+        ]
+)
 internal class ContentScannerRealmModule
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/contentscanner/db/RealmContentScannerStore.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/contentscanner/db/RealmContentScannerStore.kt
index 947a66c8b9..27729d38c7 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/contentscanner/db/RealmContentScannerStore.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/contentscanner/db/RealmContentScannerStore.kt
@@ -32,12 +32,14 @@ import org.matrix.android.sdk.internal.di.ContentScannerDatabase
 import org.matrix.android.sdk.internal.session.SessionScope
 import org.matrix.android.sdk.internal.session.contentscanner.data.ContentScannerStore
 import org.matrix.android.sdk.internal.util.isValidUrl
+import org.matrix.android.sdk.internal.util.time.Clock
 import javax.inject.Inject
 
 @SessionScope
 internal class RealmContentScannerStore @Inject constructor(
         @ContentScannerDatabase
-        private val realmConfiguration: RealmConfiguration
+        private val realmConfiguration: RealmConfiguration,
+        private val clock: Clock,
 ) : ContentScannerStore {
 
     private val monarchy = Monarchy.Builder()
@@ -82,15 +84,15 @@ internal class RealmContentScannerStore @Inject constructor(
 
     override fun updateStateForContent(mxcUrl: String, state: ScanState, scannerUrl: String?) {
         monarchy.runTransactionSync {
-            ContentScanResultEntity.getOrCreate(it, mxcUrl, scannerUrl).scanResult = state
+            ContentScanResultEntity.getOrCreate(it, mxcUrl, scannerUrl, clock.epochMillis()).scanResult = state
         }
     }
 
     override fun updateScanResultForContent(mxcUrl: String, scannerUrl: String?, state: ScanState, humanReadable: String) {
         monarchy.runTransactionSync {
-            ContentScanResultEntity.getOrCreate(it, mxcUrl, scannerUrl).apply {
+            ContentScanResultEntity.getOrCreate(it, mxcUrl, scannerUrl, clock.epochMillis()).apply {
                 scanResult = state
-                scanDateTimestamp = System.currentTimeMillis()
+                scanDateTimestamp = clock.epochMillis()
                 humanReadableMessage = humanReadable
             }
         }
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/contentscanner/model/ScanResponse.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/contentscanner/model/ScanResponse.kt
index f783fe0a6c..85d7f2e094 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/contentscanner/model/ScanResponse.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/contentscanner/model/ScanResponse.kt
@@ -20,10 +20,14 @@ import com.squareup.moshi.Json
 import com.squareup.moshi.JsonClass
 
 /**
+ * Example:
+ * 
  * {
  *      "clean": true,
  *      "info": "File clean at 6/7/2018, 6:02:40 PM"
  *  }
+ * 
+ * . */ @JsonClass(generateAdapter = true) internal data class ScanResponse( diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/directory/DirectoryAPI.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/directory/DirectoryAPI.kt index 16c57baafc..19b9130fc4 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/directory/DirectoryAPI.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/directory/DirectoryAPI.kt @@ -61,7 +61,7 @@ internal interface DirectoryAPI { @Body body: AddRoomAliasBody) /** - * Delete a room alias + * Delete a room alias. * @param roomAlias the room alias. */ @DELETE(NetworkConstants.URI_API_PREFIX_PATH_R0 + "directory/room/{roomAlias}") diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/filter/FilterApi.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/filter/FilterApi.kt index 2809dea23b..dab801360f 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/filter/FilterApi.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/filter/FilterApi.kt @@ -25,7 +25,7 @@ import retrofit2.http.Path internal interface FilterApi { /** - * Upload FilterBody to get a filter_id which can be used for /sync requests + * Upload FilterBody to get a filter_id which can be used for /sync requests. * * @param userId the user id * @param body the Json representation of a FilterBody object @@ -35,7 +35,7 @@ internal interface FilterApi { @Body body: Filter): FilterResponse /** - * Gets a filter with a given filterId from the homeserver + * Gets a filter with a given filterId from the homeserver. * * @param userId the user id * @param filterId the filterID diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/filter/FilterRepository.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/filter/FilterRepository.kt index df61539547..f40231c8cf 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/filter/FilterRepository.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/filter/FilterRepository.kt @@ -19,22 +19,22 @@ package org.matrix.android.sdk.internal.session.filter internal interface FilterRepository { /** - * Return true if the filterBody has changed, or need to be sent to the server + * Return true if the filterBody has changed, or need to be sent to the server. */ suspend fun storeFilter(filter: Filter, roomEventFilter: RoomEventFilter): Boolean /** - * Set the filterId of this filter + * Set the filterId of this filter. */ suspend fun storeFilterId(filter: Filter, filterId: String) /** - * Return filter json or filter id + * Return filter json or filter id. */ suspend fun getFilter(): String /** - * Return the room filter + * Return the room filter. */ suspend fun getRoomFilter(): String } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/filter/FilterUtil.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/filter/FilterUtil.kt index bd7f0ad402..562fea88b6 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/filter/FilterUtil.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/filter/FilterUtil.kt @@ -81,8 +81,7 @@ internal object FilterUtil { } */ /** - * Compute a new filter to enable or disable the lazy loading - * + * Compute a new filter to enable or disable the lazy loading. * * If lazy loading is on, the filter will looks like * {"room":{"state":{"lazy_load_members":true})} diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/filter/RoomFilter.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/filter/RoomFilter.kt index 585d013eae..72b1af52b8 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/filter/RoomFilter.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/filter/RoomFilter.kt @@ -40,7 +40,7 @@ internal data class RoomFilter( */ @Json(name = "ephemeral") val ephemeral: RoomEventFilter? = null, /** - * Include rooms that the user has left in the sync, default false + * Include rooms that the user has left in the sync, default false. */ @Json(name = "include_leave") val includeLeave: Boolean? = null, /** diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/filter/SaveFilterTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/filter/SaveFilterTask.kt index 3cac89ce28..e9d5b876a8 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/filter/SaveFilterTask.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/filter/SaveFilterTask.kt @@ -24,7 +24,7 @@ import org.matrix.android.sdk.internal.task.Task import javax.inject.Inject /** - * Save a filter, in db and if any changes, upload to the server + * Save a filter, in db and if any changes, upload to the server. */ internal interface SaveFilterTask : Task { diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/group/GetGroupDataWorker.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/group/GetGroupDataWorker.kt index 716859f195..21582cb4be 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/group/GetGroupDataWorker.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/group/GetGroupDataWorker.kt @@ -26,8 +26,8 @@ import org.matrix.android.sdk.internal.worker.SessionWorkerParams import javax.inject.Inject /** - * Possible previous worker: None - * Possible next worker : None + * Possible previous worker: None. + * Possible next worker : None. */ internal class GetGroupDataWorker(context: Context, params: WorkerParameters, sessionManager: SessionManager) : SessionSafeCoroutineWorker(context, params, sessionManager, Params::class.java) { diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/group/GroupAPI.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/group/GroupAPI.kt index 58dcc57dd6..c9d25b9104 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/group/GroupAPI.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/group/GroupAPI.kt @@ -26,7 +26,7 @@ import retrofit2.http.Path internal interface GroupAPI { /** - * Request a group summary + * Request a group summary. * * @param groupId the group id */ diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/group/model/GroupSummaryRoomsSection.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/group/model/GroupSummaryRoomsSection.kt index 8f9b29ed0a..87d07167ce 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/group/model/GroupSummaryRoomsSection.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/group/model/GroupSummaryRoomsSection.kt @@ -29,6 +29,6 @@ internal data class GroupSummaryRoomsSection( @Json(name = "rooms") val rooms: List = emptyList() - // @TODO: Check the meaning and the usage of these categories. This dictionary is empty FTM. + // TODO Check the meaning and the usage of these categories. This dictionary is empty FTM. // public Map categories; ) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/group/model/GroupSummaryUsersSection.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/group/model/GroupSummaryUsersSection.kt index 799aa8a5b1..63608c582a 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/group/model/GroupSummaryUsersSection.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/group/model/GroupSummaryUsersSection.kt @@ -30,6 +30,6 @@ internal data class GroupSummaryUsersSection( @Json(name = "users") val users: List = emptyList() - // @TODO: Check the meaning and the usage of these roles. This dictionary is empty FTM. + // TODO Check the meaning and the usage of these roles. This dictionary is empty FTM. // public Map roles; ) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/homeserver/CapabilitiesAPI.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/homeserver/CapabilitiesAPI.kt index 7de0cc9592..f658cda973 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/homeserver/CapabilitiesAPI.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/homeserver/CapabilitiesAPI.kt @@ -22,19 +22,19 @@ import retrofit2.http.GET internal interface CapabilitiesAPI { /** - * Request the homeserver capabilities + * Request the homeserver capabilities. */ @GET(NetworkConstants.URI_API_PREFIX_PATH_R0 + "capabilities") suspend fun getCapabilities(): GetCapabilitiesResult /** - * Request the versions + * Request the versions. */ @GET(NetworkConstants.URI_API_PREFIX_PATH_ + "versions") suspend fun getVersions(): Versions /** - * Ping the homeserver. We do not care about the returned data, so there is no use to parse them + * Ping the homeserver. We do not care about the returned data, so there is no use to parse them. */ @GET(NetworkConstants.URI_API_PREFIX_PATH_ + "versions") suspend fun ping() diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/homeserver/GetCapabilitiesResult.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/homeserver/GetCapabilitiesResult.kt index 55526b41db..95ff44807c 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/homeserver/GetCapabilitiesResult.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/homeserver/GetCapabilitiesResult.kt @@ -67,8 +67,8 @@ internal data class Capabilities( @Json(name = "m.room_versions") val roomVersions: RoomVersions? = null, /** - * Capability to indicate if the server supports MSC3440 Threading - * True if the user can use m.thread relation, false otherwise + * Capability to indicate if the server supports MSC3440 Threading. + * True if the user can use m.thread relation, false otherwise. */ @Json(name = "m.thread") val threads: BooleanCapability? = null @@ -98,6 +98,8 @@ internal data class RoomVersions( val available: JsonDict? = null, /** + * Example: + *
          *  "room_capabilities": {
          *      "knock" : {
          *              "preferred": "7",
@@ -108,6 +110,7 @@ internal data class RoomVersions(
          *              "support" : ["8", "9"]
          *      }
          * }
+         * 
. */ @Json(name = "org.matrix.msc3244.room_capabilities") val roomCapabilities: JsonDict? = null diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/homeserver/GetHomeServerCapabilitiesTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/homeserver/GetHomeServerCapabilitiesTask.kt index 44e13d971a..e9097e4d03 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/homeserver/GetHomeServerCapabilitiesTask.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/homeserver/GetHomeServerCapabilitiesTask.kt @@ -94,10 +94,12 @@ internal class DefaultGetHomeServerCapabilitiesTask @Inject constructor( }.getOrNull() val wellknownResult = runCatching { - getWellknownTask.execute(GetWellknownTask.Params( - domain = userId.getDomain(), - homeServerConnectionConfig = homeServerConnectionConfig - )) + getWellknownTask.execute( + GetWellknownTask.Params( + domain = userId.getDomain(), + homeServerConnectionConfig = homeServerConnectionConfig + ) + ) }.getOrNull() insertInDb(capabilities, mediaConfig, versions, wellknownResult) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/identity/DefaultIdentityService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/identity/DefaultIdentityService.kt index 4285f38893..74838afc65 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/identity/DefaultIdentityService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/identity/DefaultIdentityService.kt @@ -218,9 +218,11 @@ internal class DefaultIdentityService @Inject constructor( listeners.toList().forEach { tryOrNull { it.onIdentityServerChange() } } } - updateUserAccountDataTask.execute(UpdateUserAccountDataTask.IdentityParams( - identityContent = IdentityServerContent(baseUrl = url) - )) + updateUserAccountDataTask.execute( + UpdateUserAccountDataTask.IdentityParams( + identityContent = IdentityServerContent(baseUrl = url) + ) + ) } override fun getUserConsent(): Boolean { @@ -297,11 +299,13 @@ internal class DefaultIdentityService @Inject constructor( } override suspend fun sign3pidInvitation(identiyServer: String, token: String, secret: String): SignInvitationResult { - return sign3pidInvitationTask.execute(Sign3pidInvitationTask.Params( - url = identiyServer, - token = token, - privateKey = secret - )) + return sign3pidInvitationTask.execute( + Sign3pidInvitationTask.Params( + url = identiyServer, + token = token, + privateKey = secret + ) + ) } override fun addListener(listener: IdentityServiceListener) { diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/identity/IdentityAPI.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/identity/IdentityAPI.kt index 7ca8758677..aef86ed08e 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/identity/IdentityAPI.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/identity/IdentityAPI.kt @@ -34,8 +34,8 @@ import retrofit2.http.Path import retrofit2.http.Query /** - * Ref: https://matrix.org/docs/spec/identity_service/latest - * This contain the requests which need an identity server token + * Ref: https://matrix.org/docs/spec/identity_service/latest. + * This contain the requests which need an identity server token. */ internal interface IdentityAPI { /** @@ -69,8 +69,8 @@ internal interface IdentityAPI { suspend fun lookup(@Body body: IdentityLookUpParams): IdentityLookUpResponse /** - * Create a session to change the bind status of an email to an identity server - * The identity server will also send an email + * Create a session to change the bind status of an email to an identity server. + * The identity server will also send an email. * * @param body * @return the sid @@ -79,8 +79,8 @@ internal interface IdentityAPI { suspend fun requestTokenToBindEmail(@Body body: IdentityRequestTokenForEmailBody): IdentityRequestTokenResponse /** - * Create a session to change the bind status of an phone number to an identity server - * The identity server will also send an SMS on the ThreePid provided + * Create a session to change the bind status of an phone number to an identity server. + * The identity server will also send an SMS on the ThreePid provided. * * @param body * @return the sid @@ -99,9 +99,9 @@ internal interface IdentityAPI { @Body body: IdentityRequestOwnershipParams): SuccessResult /** - * https://matrix.org/docs/spec/identity_service/r0.3.0#post-matrix-identity-v2-sign-ed25519 + * https://matrix.org/docs/spec/identity_service/r0.3.0#post-matrix-identity-v2-sign-ed25519. * - * Have to rely on V1 for now + * Have to rely on V1 for now. */ @POST(NetworkConstants.URI_IDENTITY_PATH_V1 + "sign-ed25519") suspend fun signInvitationDetails( diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/identity/IdentityAuthAPI.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/identity/IdentityAuthAPI.kt index f77eb296aa..85791f59a3 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/identity/IdentityAuthAPI.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/identity/IdentityAuthAPI.kt @@ -24,14 +24,14 @@ import retrofit2.http.GET import retrofit2.http.POST /** - * Ref: https://matrix.org/docs/spec/identity_service/latest - * This contain the requests which do not need an identity server token + * Ref: https://matrix.org/docs/spec/identity_service/latest. + * This contain the requests which do not need an identity server token. */ internal interface IdentityAuthAPI { /** - * https://matrix.org/docs/spec/client_server/r0.4.0.html#server-discovery - * Simple ping call to check if server exists and is alive + * https://matrix.org/docs/spec/client_server/r0.4.0.html#server-discovery. + * Simple ping call to check if server exists and is alive. * * Ref: https://matrix.org/docs/spec/identity_service/unstable#status-check * https://matrix.org/docs/spec/identity_service/latest#get-matrix-identity-v2 @@ -42,7 +42,7 @@ internal interface IdentityAuthAPI { suspend fun ping() /** - * Ping v1 will be used to check outdated identity server + * Ping v1 will be used to check outdated identity server. */ @GET("_matrix/identity/api/v1") suspend fun pingV1() diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/identity/IdentityBulkLookupTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/identity/IdentityBulkLookupTask.kt index f6ef370f8d..f642ed4cf2 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/identity/IdentityBulkLookupTask.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/identity/IdentityBulkLookupTask.kt @@ -83,11 +83,13 @@ internal class DefaultIdentityBulkLookupTask @Inject constructor( return try { LookUpData(hashedAddresses, executeRequest(null) { - identityAPI.lookup(IdentityLookUpParams( - hashedAddresses, - IdentityHashDetailResponse.ALGORITHM_SHA256, - hashDetailResponse.pepper - )) + identityAPI.lookup( + IdentityLookUpParams( + hashedAddresses, + IdentityHashDetailResponse.ALGORITHM_SHA256, + hashDetailResponse.pepper + ) + ) }) } catch (failure: Throwable) { // Catch invalid hash pepper and retry @@ -117,8 +119,10 @@ internal class DefaultIdentityBulkLookupTask @Inject constructor( return withOlmUtility { olmUtility -> threePids.map { threePid -> base64ToBase64Url( - olmUtility.sha256(threePid.value.lowercase(Locale.ROOT) + - " " + threePid.toMedium() + " " + pepper) + olmUtility.sha256( + threePid.value.lowercase(Locale.ROOT) + + " " + threePid.toMedium() + " " + pepper + ) ) } } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/identity/IdentityRequestTokenForBindingTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/identity/IdentityRequestTokenForBindingTask.kt index 9c89048176..fe12309650 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/identity/IdentityRequestTokenForBindingTask.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/identity/IdentityRequestTokenForBindingTask.kt @@ -57,18 +57,22 @@ internal class DefaultIdentityRequestTokenForBindingTask @Inject constructor( val tokenResponse = executeRequest(null) { when (params.threePid) { - is ThreePid.Email -> identityAPI.requestTokenToBindEmail(IdentityRequestTokenForEmailBody( - clientSecret = clientSecret, - sendAttempt = sendAttempt, - email = params.threePid.email - )) + is ThreePid.Email -> identityAPI.requestTokenToBindEmail( + IdentityRequestTokenForEmailBody( + clientSecret = clientSecret, + sendAttempt = sendAttempt, + email = params.threePid.email + ) + ) is ThreePid.Msisdn -> { - identityAPI.requestTokenToBindMsisdn(IdentityRequestTokenForMsisdnBody( - clientSecret = clientSecret, - sendAttempt = sendAttempt, - phoneNumber = params.threePid.msisdn, - countryCode = params.threePid.getCountryCode() - )) + identityAPI.requestTokenToBindMsisdn( + IdentityRequestTokenForMsisdnBody( + clientSecret = clientSecret, + sendAttempt = sendAttempt, + phoneNumber = params.threePid.msisdn, + countryCode = params.threePid.getCountryCode() + ) + ) } } } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/identity/IdentitySubmitTokenForBindingTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/identity/IdentitySubmitTokenForBindingTask.kt index f884e2816d..fcf8ce6317 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/identity/IdentitySubmitTokenForBindingTask.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/identity/IdentitySubmitTokenForBindingTask.kt @@ -50,7 +50,8 @@ internal class DefaultIdentitySubmitTokenForBindingTask @Inject constructor( clientSecret = identityPendingBinding.clientSecret, sid = identityPendingBinding.sid, token = params.token - )) + ) + ) } if (!tokenResponse.isSuccess()) { diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/identity/data/IdentityStore.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/identity/data/IdentityStore.kt index 0e05224be5..51d4ed7c6d 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/identity/data/IdentityStore.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/identity/data/IdentityStore.kt @@ -32,7 +32,7 @@ internal interface IdentityStore { fun setHashDetails(hashDetailResponse: IdentityHashDetailResponse) /** - * Store details about a current binding + * Store details about a current binding. */ fun storePendingBinding(threePid: ThreePid, data: IdentityPendingBinding) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/identity/db/IdentityDataEntityQuery.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/identity/db/IdentityDataEntityQuery.kt index 5152e33743..aecf21678c 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/identity/db/IdentityDataEntityQuery.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/identity/db/IdentityDataEntityQuery.kt @@ -22,7 +22,7 @@ import io.realm.kotlin.createObject import io.realm.kotlin.where /** - * Only one object can be stored at a time + * Only one object can be stored at a time. */ internal fun IdentityDataEntity.Companion.get(realm: Realm): IdentityDataEntity? { return realm.where().findFirst() diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/identity/db/IdentityRealmModule.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/identity/db/IdentityRealmModule.kt index 1f2cfad33c..a5ec6061ba 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/identity/db/IdentityRealmModule.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/identity/db/IdentityRealmModule.kt @@ -19,11 +19,13 @@ package org.matrix.android.sdk.internal.session.identity.db import io.realm.annotations.RealmModule /** - * Realm module for identity server classes + * Realm module for identity server classes. */ -@RealmModule(library = true, +@RealmModule( + library = true, classes = [ IdentityDataEntity::class, IdentityPendingBindingEntity::class - ]) + ] +) internal class IdentityRealmModule diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/identity/db/RealmIdentityStoreMigration.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/identity/db/RealmIdentityStoreMigration.kt index 0c279d8a7e..e731f9f347 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/identity/db/RealmIdentityStoreMigration.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/identity/db/RealmIdentityStoreMigration.kt @@ -24,8 +24,8 @@ import javax.inject.Inject internal class RealmIdentityStoreMigration @Inject constructor() : RealmMigration { /** - * Forces all RealmIdentityStoreMigration instances to be equal - * Avoids Realm throwing when multiple instances of the migration are set + * Forces all RealmIdentityStoreMigration instances to be equal. + * Avoids Realm throwing when multiple instances of the migration are set. */ override fun equals(other: Any?) = other is RealmIdentityStoreMigration override fun hashCode() = 3000 diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/identity/model/SignInvitationBody.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/identity/model/SignInvitationBody.kt index 6998257263..465d296e94 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/identity/model/SignInvitationBody.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/identity/model/SignInvitationBody.kt @@ -16,14 +16,16 @@ package org.matrix.android.sdk.internal.session.identity.model +import com.squareup.moshi.Json import com.squareup.moshi.JsonClass @JsonClass(generateAdapter = true) internal data class SignInvitationBody( - /**The Matrix user ID of the user accepting the invitation.*/ + /** The Matrix user ID of the user accepting the invitation.*/ val mxid: String, - /**The token from the call to store- invite..*/ + /** The token from the call to store- invite..*/ val token: String, /** The private key, encoded as Unpadded base64. */ - val private_key: String + @Json(name = "private_key") + val privateKey: String ) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/initsync/DefaultSyncStatusService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/initsync/DefaultSyncStatusService.kt index 4a1e6661b0..2aa0be80c9 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/initsync/DefaultSyncStatusService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/initsync/DefaultSyncStatusService.kt @@ -24,7 +24,7 @@ import javax.inject.Inject @SessionScope internal class DefaultSyncStatusService @Inject constructor() : - SyncStatusService, + SyncStatusService, ProgressReporter { private val status = MutableLiveData() @@ -41,7 +41,7 @@ internal class DefaultSyncStatusService @Inject constructor() : } /** - * Create a rootTask + * Create a rootTask. */ fun startRoot(initSyncStep: InitSyncStep, totalProgress: Int) { @@ -51,7 +51,7 @@ internal class DefaultSyncStatusService @Inject constructor() : } /** - * Add a child to the leaf + * Add a child to the leaf. */ override fun startTask(initSyncStep: InitSyncStep, totalProgress: Int, diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/initsync/TaskInfo.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/initsync/TaskInfo.kt index 3e4cce2e1f..fef16bf1d7 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/initsync/TaskInfo.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/initsync/TaskInfo.kt @@ -29,7 +29,7 @@ internal class TaskInfo(val initSyncStep: InitSyncStep, private val offset = parent?.currentProgress ?: 0F /** - * Get the further child + * Get the further child. */ fun leaf(): TaskInfo { var last = this @@ -40,7 +40,7 @@ internal class TaskInfo(val initSyncStep: InitSyncStep, } /** - * Set progress of this task and update the parent progress iteratively + * Set progress of this task and update the parent progress iteratively. */ fun setProgress(progress: Float) { Timber.v("setProgress: $progress / $totalProgress") diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/integrationmanager/AllowedWidgetsContent.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/integrationmanager/AllowedWidgetsContent.kt index 8d0e8c930d..ebf14c602c 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/integrationmanager/AllowedWidgetsContent.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/integrationmanager/AllowedWidgetsContent.kt @@ -22,18 +22,20 @@ import com.squareup.moshi.JsonClass @JsonClass(generateAdapter = true) internal data class AllowedWidgetsContent( /** - * Map of stateEventId to Allowed + * Map of stateEventId to Allowed. */ @Json(name = "widgets") val widgets: Map = emptyMap(), /** - * Map of native widgetType to a map of domain to Allowed + * Map of native widgetType to a map of domain to Allowed. + *
          * {
          *      "jitsi" : {
          *            "jitsi.domain.org"  : true,
          *            "jitsi.other.org"  : false
          *      }
          * }
+         * 
*/ @Json(name = "native_widgets") val native: Map> = emptyMap() ) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/integrationmanager/IntegrationManager.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/integrationmanager/IntegrationManager.kt index 30b1589169..8034e5b974 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/integrationmanager/IntegrationManager.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/integrationmanager/IntegrationManager.kt @@ -59,7 +59,7 @@ internal class IntegrationManager @Inject constructor(matrixConfiguration: Matri private val updateUserAccountDataTask: UpdateUserAccountDataTask, private val accountDataDataSource: UserAccountDataDataSource, private val widgetFactory: WidgetFactory) : - SessionLifecycleObserver { + SessionLifecycleObserver { private val currentConfigs = ArrayList() private val lifecycleOwner: LifecycleOwner = LifecycleOwner { lifecycleRegistry } @@ -124,7 +124,7 @@ internal class IntegrationManager @Inject constructor(matrixConfiguration: Matri } /** - * Returns false if the user as disabled integration manager feature + * Returns false if the user as disabled integration manager feature. */ fun isIntegrationEnabled(): Boolean { val integrationProvisioningData = accountDataDataSource.getAccountDataEvent(UserAccountDataTypes.TYPE_INTEGRATION_PROVISIONING) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/media/PreviewUrlMapper.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/media/PreviewUrlMapper.kt index 551dc29b92..06fbf802ae 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/media/PreviewUrlMapper.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/media/PreviewUrlMapper.kt @@ -20,7 +20,7 @@ import org.matrix.android.sdk.api.session.media.PreviewUrlData import org.matrix.android.sdk.internal.database.model.PreviewUrlCacheEntity /** - * PreviewUrlCacheEntity -> PreviewUrlData + * PreviewUrlCacheEntity -> PreviewUrlData. */ internal fun PreviewUrlCacheEntity.toDomain() = PreviewUrlData( url = urlFromServer ?: url, diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/permalinks/PermalinkFactory.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/permalinks/PermalinkFactory.kt index 0aeb0467de..f9da7b66f6 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/permalinks/PermalinkFactory.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/permalinks/PermalinkFactory.kt @@ -126,7 +126,7 @@ internal class PermalinkFactory @Inject constructor( } /** - * Escape '/' in id, because it is used as a separator + * Escape '/' in id, because it is used as a separator. * * @param id the id to escape * @return the escaped id @@ -136,7 +136,7 @@ internal class PermalinkFactory @Inject constructor( } /** - * Unescape '/' in id + * Unescape '/' in id. * * @param id the id to escape * @return the escaped id diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/permalinks/ViaParameterFinder.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/permalinks/ViaParameterFinder.kt index dff82cb42e..0f667c65df 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/permalinks/ViaParameterFinder.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/permalinks/ViaParameterFinder.kt @@ -68,10 +68,11 @@ internal class ViaParameterFinder @Inject constructor( } /** - * Get a set of userIds of joined members of a room + * Get a set of userIds of joined members of a room. */ private fun getUserIdsOfJoinedMembers(roomId: String): Set { return roomGetterProvider.get().getRoom(roomId) + ?.membershipService() ?.getRoomMembers(roomMemberQueryParams { memberships = listOf(Membership.JOIN) }) ?.map { it.userId } .orEmpty() @@ -84,6 +85,7 @@ internal class ViaParameterFinder @Inject constructor( // It may not be possible for a user to join a room if there's no overlap between these fun computeViaParamsForRestricted(roomId: String, max: Int): List { val userThatCanInvite = roomGetterProvider.get().getRoom(roomId) + ?.membershipService() ?.getRoomMembers(roomMemberQueryParams { memberships = listOf(Membership.JOIN) }) ?.map { it.userId } ?.filter { userCanInvite(userId, roomId) } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/presence/model/PresenceContent.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/presence/model/PresenceContent.kt index b1ca512652..abad91bad2 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/presence/model/PresenceContent.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/presence/model/PresenceContent.kt @@ -21,7 +21,7 @@ import com.squareup.moshi.JsonClass import org.matrix.android.sdk.api.session.presence.model.PresenceEnum /** - * Class representing the EventType.PRESENCE event content + * Class representing the EventType.PRESENCE event content. */ @JsonClass(generateAdapter = true) internal data class PresenceContent( @@ -38,7 +38,7 @@ internal data class PresenceContent( */ @Json(name = "status_msg") val statusMessage: String? = null, /** - * Whether the user is currently active + * Whether the user is currently active. */ @Json(name = "currently_active") val isCurrentlyActive: Boolean = false, /** diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/profile/AccountThreePidsResponse.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/profile/AccountThreePidsResponse.kt index 0a792397f8..6d2b3c480d 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/profile/AccountThreePidsResponse.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/profile/AccountThreePidsResponse.kt @@ -19,7 +19,7 @@ import com.squareup.moshi.Json import com.squareup.moshi.JsonClass /** - * Class representing the ThreePids response + * Class representing the ThreePids response. */ @JsonClass(generateAdapter = true) internal data class AccountThreePidsResponse( diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/profile/BindThreePidsTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/profile/BindThreePidsTask.kt index 87e51181e6..f630c2c225 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/profile/BindThreePidsTask.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/profile/BindThreePidsTask.kt @@ -50,7 +50,8 @@ internal class DefaultBindThreePidsTask @Inject constructor(private val profileA identityServerUrlWithoutProtocol = identityServerUrlWithoutProtocol, identityServerAccessToken = identityServerAccessToken, sid = identityPendingBinding.sid - )) + ) + ) } // Binding is over, cleanup the store diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/profile/DefaultProfileService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/profile/DefaultProfileService.kt index 6f99577ac2..5e64a6af0e 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/profile/DefaultProfileService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/profile/DefaultProfileService.kt @@ -135,21 +135,25 @@ internal class DefaultProfileService @Inject constructor(private val taskExecuto override suspend fun finalizeAddingThreePid(threePid: ThreePid, userInteractiveAuthInterceptor: UserInteractiveAuthInterceptor) { finalizeAddingThreePidTask - .execute(FinalizeAddingThreePidTask.Params( - threePid = threePid, - userInteractiveAuthInterceptor = userInteractiveAuthInterceptor, - userWantsToCancel = false - )) + .execute( + FinalizeAddingThreePidTask.Params( + threePid = threePid, + userInteractiveAuthInterceptor = userInteractiveAuthInterceptor, + userWantsToCancel = false + ) + ) refreshThreePids() } override suspend fun cancelAddingThreePid(threePid: ThreePid) { finalizeAddingThreePidTask - .execute(FinalizeAddingThreePidTask.Params( - threePid = threePid, - userInteractiveAuthInterceptor = null, - userWantsToCancel = true - )) + .execute( + FinalizeAddingThreePidTask.Params( + threePid = threePid, + userInteractiveAuthInterceptor = null, + userWantsToCancel = true + ) + ) refreshThreePids() } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/profile/FinalizeAddingThreePidTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/profile/FinalizeAddingThreePidTask.kt index 6ff4efaf11..501aff63bd 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/profile/FinalizeAddingThreePidTask.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/profile/FinalizeAddingThreePidTask.kt @@ -22,6 +22,7 @@ import org.matrix.android.sdk.api.auth.UserInteractiveAuthInterceptor import org.matrix.android.sdk.api.failure.Failure import org.matrix.android.sdk.api.failure.toRegistrationFlowResponse import org.matrix.android.sdk.api.session.identity.ThreePid +import org.matrix.android.sdk.api.session.uia.UiaResult import org.matrix.android.sdk.internal.auth.registration.handleUIA import org.matrix.android.sdk.internal.database.model.PendingThreePidEntity import org.matrix.android.sdk.internal.database.model.PendingThreePidEntityFields @@ -72,13 +73,13 @@ internal class DefaultFinalizeAddingThreePidTask @Inject constructor( true } catch (throwable: Throwable) { if (params.userInteractiveAuthInterceptor == null || - !handleUIA( + handleUIA( failure = throwable, interceptor = params.userInteractiveAuthInterceptor, retryBlock = { authUpdate -> execute(params.copy(userAuthParam = authUpdate)) } - ) + ) != UiaResult.SUCCESS ) { Timber.d("## UIA: propagate failure") throw throwable.toRegistrationFlowResponse() diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/profile/ProfileAPI.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/profile/ProfileAPI.kt index 4b56db9f13..4d4506be76 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/profile/ProfileAPI.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/profile/ProfileAPI.kt @@ -46,7 +46,7 @@ internal interface ProfileAPI { suspend fun getThreePIDs(): AccountThreePidsResponse /** - * Change user display name + * Change user display name. */ @PUT(NetworkConstants.URI_API_PREFIX_PATH_R0 + "profile/{userId}/displayname") suspend fun setDisplayName(@Path("userId") userId: String, @@ -86,7 +86,7 @@ internal interface ProfileAPI { suspend fun addMsisdn(@Body body: AddMsisdnBody): AddMsisdnResponse /** - * Validate Msisdn code (same model than for identity server API) + * Validate Msisdn code (same model than for identity server API). */ @POST suspend fun validateMsisdn(@Url url: String, diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/profile/UnbindThreePidsTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/profile/UnbindThreePidsTask.kt index df8a1c97ff..acbecc9fbe 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/profile/UnbindThreePidsTask.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/profile/UnbindThreePidsTask.kt @@ -45,7 +45,8 @@ internal class DefaultUnbindThreePidsTask @Inject constructor(private val profil identityServerUrlWithoutProtocol, params.threePid.toMedium(), params.threePid.value - )) + ) + ) }.isSuccess() } } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/pushers/AddPushRuleTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/pushers/AddPushRuleTask.kt index b217687168..c46474cf90 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/pushers/AddPushRuleTask.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/pushers/AddPushRuleTask.kt @@ -15,8 +15,8 @@ */ package org.matrix.android.sdk.internal.session.pushers -import org.matrix.android.sdk.api.pushrules.RuleKind -import org.matrix.android.sdk.api.pushrules.rest.PushRule +import org.matrix.android.sdk.api.session.pushrules.RuleKind +import org.matrix.android.sdk.api.session.pushrules.rest.PushRule import org.matrix.android.sdk.internal.network.GlobalErrorReceiver import org.matrix.android.sdk.internal.network.executeRequest import org.matrix.android.sdk.internal.task.Task diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/pushers/AddPusherWorker.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/pushers/AddPusherWorker.kt index ce29efaaac..0042558027 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/pushers/AddPusherWorker.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/pushers/AddPusherWorker.kt @@ -26,7 +26,7 @@ import org.matrix.android.sdk.internal.worker.SessionWorkerParams import javax.inject.Inject internal class AddPusherWorker(context: Context, params: WorkerParameters, sessionManager: SessionManager) : - SessionSafeCoroutineWorker(context, params, sessionManager, Params::class.java) { + SessionSafeCoroutineWorker(context, params, sessionManager, Params::class.java) { @JsonClass(generateAdapter = true) internal data class Params( diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/pushers/DefaultConditionResolver.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/pushers/DefaultConditionResolver.kt index 84a05067be..67fba390d0 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/pushers/DefaultConditionResolver.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/pushers/DefaultConditionResolver.kt @@ -15,14 +15,15 @@ */ package org.matrix.android.sdk.internal.session.pushers -import org.matrix.android.sdk.api.pushrules.ConditionResolver -import org.matrix.android.sdk.api.pushrules.ContainsDisplayNameCondition -import org.matrix.android.sdk.api.pushrules.EventMatchCondition -import org.matrix.android.sdk.api.pushrules.RoomMemberCountCondition -import org.matrix.android.sdk.api.pushrules.SenderNotificationPermissionCondition import org.matrix.android.sdk.api.session.events.model.Event import org.matrix.android.sdk.api.session.events.model.EventType import org.matrix.android.sdk.api.session.events.model.toModel +import org.matrix.android.sdk.api.session.pushrules.ConditionResolver +import org.matrix.android.sdk.api.session.pushrules.ContainsDisplayNameCondition +import org.matrix.android.sdk.api.session.pushrules.EventMatchCondition +import org.matrix.android.sdk.api.session.pushrules.RoomMemberCountCondition +import org.matrix.android.sdk.api.session.pushrules.SenderNotificationPermissionCondition +import org.matrix.android.sdk.api.session.room.getStateEvent import org.matrix.android.sdk.api.session.room.model.PowerLevelsContent import org.matrix.android.sdk.internal.di.UserId import org.matrix.android.sdk.internal.session.room.RoomGetter @@ -60,7 +61,7 @@ internal class DefaultConditionResolver @Inject constructor( condition: ContainsDisplayNameCondition): Boolean { val roomId = event.roomId ?: return false val room = roomGetter.getRoom(roomId) ?: return false - val myDisplayName = room.getRoomMember(userId)?.displayName ?: return false + val myDisplayName = room.membershipService().getRoomMember(userId)?.displayName ?: return false return condition.isSatisfied(event, myDisplayName) } } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/pushers/DefaultPushersService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/pushers/DefaultPushersService.kt index e87c27e601..13b990a9ff 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/pushers/DefaultPushersService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/pushers/DefaultPushersService.kt @@ -85,17 +85,19 @@ internal class DefaultPushersService @Inject constructor( deviceDisplayName: String, append: Boolean) { addPusherTask.execute( - AddPusherTask.Params(JsonPusher( - pushKey = email, - kind = Pusher.KIND_EMAIL, - appId = Pusher.APP_ID_EMAIL, - profileTag = "", - lang = lang, - appDisplayName = appDisplayName, - deviceDisplayName = deviceDisplayName, - data = JsonPusherData(brand = emailBranding), - append = append - )) + AddPusherTask.Params( + JsonPusher( + pushKey = email, + kind = Pusher.KIND_EMAIL, + appId = Pusher.APP_ID_EMAIL, + profileTag = "", + lang = lang, + appDisplayName = appDisplayName, + deviceDisplayName = deviceDisplayName, + data = JsonPusherData(brand = emailBranding), + append = append + ) + ) ) } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/pushrules/rest/GetPushRulesResponse.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/pushers/GetPushRulesResponse.kt similarity index 81% rename from matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/pushrules/rest/GetPushRulesResponse.kt rename to matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/pushers/GetPushRulesResponse.kt index 35b4d77c0e..5f35c919fc 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/pushrules/rest/GetPushRulesResponse.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/pushers/GetPushRulesResponse.kt @@ -13,10 +13,11 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.matrix.android.sdk.api.pushrules.rest +package org.matrix.android.sdk.internal.session.pushers import com.squareup.moshi.Json import com.squareup.moshi.JsonClass +import org.matrix.android.sdk.api.session.pushrules.rest.RuleSet /** * All push rulesets for a user. @@ -25,13 +26,13 @@ import com.squareup.moshi.JsonClass @JsonClass(generateAdapter = true) internal data class GetPushRulesResponse( /** - * Global rules, account level applying to all devices + * Global rules, account level applying to all devices. */ @Json(name = "global") val global: RuleSet, /** - * Device specific rules, apply only to current device + * Device specific rules, apply only to current device. */ @Json(name = "device") val device: RuleSet? = null diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/pushers/GetPushRulesTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/pushers/GetPushRulesTask.kt index 8cf861d285..8e7078292b 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/pushers/GetPushRulesTask.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/pushers/GetPushRulesTask.kt @@ -25,7 +25,7 @@ internal interface GetPushRulesTask : Task { } /** - * We keep this task, but it should not be used anymore, the push rules comes from the sync response + * We keep this task, but it should not be used anymore, the push rules comes from the sync response. */ internal class DefaultGetPushRulesTask @Inject constructor( private val pushRulesApi: PushRulesApi, diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/pushers/JsonPusher.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/pushers/JsonPusher.kt index 8dc0954694..71a1ea8c66 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/pushers/JsonPusher.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/pushers/JsonPusher.kt @@ -22,7 +22,6 @@ import java.security.InvalidParameterException /** * Example: - * * * { * "pushers": [ @@ -40,6 +39,7 @@ import java.security.InvalidParameterException * }] * } * + * . */ @JsonClass(generateAdapter = true) internal data class JsonPusher( diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/pushers/PushRulesApi.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/pushers/PushRulesApi.kt index 994b4860c6..40b4ee269a 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/pushers/PushRulesApi.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/pushers/PushRulesApi.kt @@ -15,8 +15,7 @@ */ package org.matrix.android.sdk.internal.session.pushers -import org.matrix.android.sdk.api.pushrules.rest.GetPushRulesResponse -import org.matrix.android.sdk.api.pushrules.rest.PushRule +import org.matrix.android.sdk.api.session.pushrules.rest.PushRule import org.matrix.android.sdk.internal.network.NetworkConstants import retrofit2.http.Body import retrofit2.http.DELETE @@ -26,13 +25,13 @@ import retrofit2.http.Path internal interface PushRulesApi { /** - * Get all push rules + * Get all push rules. */ @GET(NetworkConstants.URI_API_PREFIX_PATH_R0 + "pushrules/") suspend fun getAllRules(): GetPushRulesResponse /** - * Update the ruleID enable status + * Update the ruleID enable status. * * @param kind the notification kind (sender, room...) * @param ruleId the ruleId @@ -44,7 +43,7 @@ internal interface PushRulesApi { @Body enabledBody: EnabledBody) /** - * Update the ruleID action + * Update the ruleID action. * Ref: https://matrix.org/docs/spec/client_server/latest#put-matrix-client-r0-pushrules-scope-kind-ruleid-actions * * @param kind the notification kind (sender, room...) @@ -57,7 +56,7 @@ internal interface PushRulesApi { @Body actions: Any) /** - * Delete a rule + * Delete a rule. * * @param kind the notification kind (sender, room...) * @param ruleId the ruleId @@ -67,7 +66,7 @@ internal interface PushRulesApi { @Path("ruleId") ruleId: String) /** - * Add the ruleID enable status + * Add the ruleID enable status. * * @param kind the notification kind (sender, room...) * @param ruleId the ruleId. diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/pushers/PushersModule.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/pushers/PushersModule.kt index d53a4eed65..4528c95e69 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/pushers/PushersModule.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/pushers/PushersModule.kt @@ -19,14 +19,14 @@ package org.matrix.android.sdk.internal.session.pushers import dagger.Binds import dagger.Module import dagger.Provides -import org.matrix.android.sdk.api.pushrules.ConditionResolver -import org.matrix.android.sdk.api.pushrules.PushRuleService import org.matrix.android.sdk.api.session.pushers.PushersService -import org.matrix.android.sdk.internal.session.notification.DefaultProcessEventForPushTask -import org.matrix.android.sdk.internal.session.notification.DefaultPushRuleService -import org.matrix.android.sdk.internal.session.notification.ProcessEventForPushTask +import org.matrix.android.sdk.api.session.pushrules.ConditionResolver +import org.matrix.android.sdk.api.session.pushrules.PushRuleService import org.matrix.android.sdk.internal.session.pushers.gateway.DefaultPushGatewayNotifyTask import org.matrix.android.sdk.internal.session.pushers.gateway.PushGatewayNotifyTask +import org.matrix.android.sdk.internal.session.pushrules.DefaultProcessEventForPushTask +import org.matrix.android.sdk.internal.session.pushrules.DefaultPushRuleService +import org.matrix.android.sdk.internal.session.pushrules.ProcessEventForPushTask import org.matrix.android.sdk.internal.session.room.notification.DefaultSetRoomNotificationStateTask import org.matrix.android.sdk.internal.session.room.notification.SetRoomNotificationStateTask import retrofit2.Retrofit diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/pushers/RemovePushRuleTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/pushers/RemovePushRuleTask.kt index bae893608b..9b0bf7934b 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/pushers/RemovePushRuleTask.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/pushers/RemovePushRuleTask.kt @@ -15,7 +15,7 @@ */ package org.matrix.android.sdk.internal.session.pushers -import org.matrix.android.sdk.api.pushrules.RuleKind +import org.matrix.android.sdk.api.session.pushrules.RuleKind import org.matrix.android.sdk.internal.network.GlobalErrorReceiver import org.matrix.android.sdk.internal.network.executeRequest import org.matrix.android.sdk.internal.task.Task diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/pushers/SavePushRulesTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/pushers/SavePushRulesTask.kt index 6a4b891ecf..88c78aa460 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/pushers/SavePushRulesTask.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/pushers/SavePushRulesTask.kt @@ -16,9 +16,8 @@ package org.matrix.android.sdk.internal.session.pushers import com.zhuinden.monarchy.Monarchy -import org.matrix.android.sdk.api.pushrules.RuleScope -import org.matrix.android.sdk.api.pushrules.RuleSetKey -import org.matrix.android.sdk.api.pushrules.rest.GetPushRulesResponse +import org.matrix.android.sdk.api.session.pushrules.RuleScope +import org.matrix.android.sdk.api.session.pushrules.RuleSetKey import org.matrix.android.sdk.internal.database.mapper.PushRulesMapper import org.matrix.android.sdk.internal.database.model.PushRulesEntity import org.matrix.android.sdk.internal.database.model.deleteOnCascade @@ -28,7 +27,7 @@ import org.matrix.android.sdk.internal.util.awaitTransaction import javax.inject.Inject /** - * Save the push rules in DB + * Save the push rules in DB. */ internal interface SavePushRulesTask : Task { data class Params(val pushRules: GetPushRulesResponse) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/pushers/UpdatePushRuleActionsTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/pushers/UpdatePushRuleActionsTask.kt index 33589dc55b..454b9cdd80 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/pushers/UpdatePushRuleActionsTask.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/pushers/UpdatePushRuleActionsTask.kt @@ -15,9 +15,9 @@ */ package org.matrix.android.sdk.internal.session.pushers -import org.matrix.android.sdk.api.pushrules.Action -import org.matrix.android.sdk.api.pushrules.RuleKind -import org.matrix.android.sdk.api.pushrules.toJson +import org.matrix.android.sdk.api.session.pushrules.Action +import org.matrix.android.sdk.api.session.pushrules.RuleKind +import org.matrix.android.sdk.api.session.pushrules.toJson import org.matrix.android.sdk.internal.network.GlobalErrorReceiver import org.matrix.android.sdk.internal.network.executeRequest import org.matrix.android.sdk.internal.task.Task @@ -38,18 +38,18 @@ internal class DefaultUpdatePushRuleActionsTask @Inject constructor( ) : UpdatePushRuleActionsTask { override suspend fun execute(params: UpdatePushRuleActionsTask.Params) { + executeRequest(globalErrorReceiver) { + pushRulesApi.updateEnableRuleStatus( + params.kind.value, + params.ruleId, + EnabledBody(params.enable) + ) + } + if (params.actions != null) { + val body = mapOf("actions" to params.actions.toJson()) executeRequest(globalErrorReceiver) { - pushRulesApi.updateEnableRuleStatus( - params.kind.value, - params.ruleId, - EnabledBody(params.enable) - ) - } - if (params.actions != null) { - val body = mapOf("actions" to params.actions.toJson()) - executeRequest(globalErrorReceiver) { - pushRulesApi.updateRuleActions(params.kind.value, params.ruleId, body) - } + pushRulesApi.updateRuleActions(params.kind.value, params.ruleId, body) } + } } } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/pushers/UpdatePushRuleEnableStatusTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/pushers/UpdatePushRuleEnableStatusTask.kt index 3fe1614678..815661a1ce 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/pushers/UpdatePushRuleEnableStatusTask.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/pushers/UpdatePushRuleEnableStatusTask.kt @@ -15,8 +15,8 @@ */ package org.matrix.android.sdk.internal.session.pushers -import org.matrix.android.sdk.api.pushrules.RuleKind -import org.matrix.android.sdk.api.pushrules.rest.PushRule +import org.matrix.android.sdk.api.session.pushrules.RuleKind +import org.matrix.android.sdk.api.session.pushrules.rest.PushRule import org.matrix.android.sdk.internal.network.GlobalErrorReceiver import org.matrix.android.sdk.internal.network.executeRequest import org.matrix.android.sdk.internal.task.Task diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/notification/DefaultPushRuleService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/pushrules/DefaultPushRuleService.kt similarity index 90% rename from matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/notification/DefaultPushRuleService.kt rename to matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/pushrules/DefaultPushRuleService.kt index cdc7350f8b..ace23f1fe5 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/notification/DefaultPushRuleService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/pushrules/DefaultPushRuleService.kt @@ -13,23 +13,23 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.matrix.android.sdk.internal.session.notification +package org.matrix.android.sdk.internal.session.pushrules import androidx.lifecycle.LiveData import androidx.lifecycle.Transformations import com.zhuinden.monarchy.Monarchy -import org.matrix.android.sdk.api.pushrules.Action -import org.matrix.android.sdk.api.pushrules.ConditionResolver -import org.matrix.android.sdk.api.pushrules.PushEvents -import org.matrix.android.sdk.api.pushrules.PushRuleService -import org.matrix.android.sdk.api.pushrules.RuleKind -import org.matrix.android.sdk.api.pushrules.RuleScope -import org.matrix.android.sdk.api.pushrules.RuleSetKey -import org.matrix.android.sdk.api.pushrules.SenderNotificationPermissionCondition -import org.matrix.android.sdk.api.pushrules.getActions -import org.matrix.android.sdk.api.pushrules.rest.PushRule -import org.matrix.android.sdk.api.pushrules.rest.RuleSet import org.matrix.android.sdk.api.session.events.model.Event +import org.matrix.android.sdk.api.session.pushrules.Action +import org.matrix.android.sdk.api.session.pushrules.ConditionResolver +import org.matrix.android.sdk.api.session.pushrules.PushEvents +import org.matrix.android.sdk.api.session.pushrules.PushRuleService +import org.matrix.android.sdk.api.session.pushrules.RuleKind +import org.matrix.android.sdk.api.session.pushrules.RuleScope +import org.matrix.android.sdk.api.session.pushrules.RuleSetKey +import org.matrix.android.sdk.api.session.pushrules.SenderNotificationPermissionCondition +import org.matrix.android.sdk.api.session.pushrules.getActions +import org.matrix.android.sdk.api.session.pushrules.rest.PushRule +import org.matrix.android.sdk.api.session.pushrules.rest.RuleSet import org.matrix.android.sdk.internal.database.mapper.PushRulesMapper import org.matrix.android.sdk.internal.database.model.PushRuleEntity import org.matrix.android.sdk.internal.database.model.PushRulesEntity diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/notification/ProcessEventForPushTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/pushrules/ProcessEventForPushTask.kt similarity index 90% rename from matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/notification/ProcessEventForPushTask.kt rename to matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/pushrules/ProcessEventForPushTask.kt index 899bce4c8d..60c1194708 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/notification/ProcessEventForPushTask.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/pushrules/ProcessEventForPushTask.kt @@ -14,12 +14,12 @@ * limitations under the License. */ -package org.matrix.android.sdk.internal.session.notification +package org.matrix.android.sdk.internal.session.pushrules -import org.matrix.android.sdk.api.pushrules.PushEvents -import org.matrix.android.sdk.api.pushrules.rest.PushRule import org.matrix.android.sdk.api.session.events.model.EventType import org.matrix.android.sdk.api.session.events.model.isInvitation +import org.matrix.android.sdk.api.session.pushrules.PushEvents +import org.matrix.android.sdk.api.session.pushrules.rest.PushRule import org.matrix.android.sdk.api.session.sync.model.RoomsSyncResponse import org.matrix.android.sdk.internal.di.UserId import org.matrix.android.sdk.internal.task.Task @@ -67,8 +67,10 @@ internal class DefaultProcessEventForPushTask @Inject constructor( }.filter { it.senderId != userId } - Timber.v("[PushRules] Found ${allEvents.size} out of ${(newJoinEvents + inviteEvents).size}" + - " to check for push rules with ${params.rules.size} rules") + Timber.v( + "[PushRules] Found ${allEvents.size} out of ${(newJoinEvents + inviteEvents).size}" + + " to check for push rules with ${params.rules.size} rules" + ) val matchedEvents = allEvents.mapNotNull { event -> pushRuleFinder.fulfilledBingRule(event, params.rules)?.let { Timber.v("[PushRules] Rule $it match for event ${event.eventId}") diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/notification/PushRuleFinder.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/pushrules/PushRuleFinder.kt similarity index 86% rename from matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/notification/PushRuleFinder.kt rename to matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/pushrules/PushRuleFinder.kt index 6e302d373d..b9d06a934d 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/notification/PushRuleFinder.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/pushrules/PushRuleFinder.kt @@ -14,11 +14,11 @@ * limitations under the License. */ -package org.matrix.android.sdk.internal.session.notification +package org.matrix.android.sdk.internal.session.pushrules -import org.matrix.android.sdk.api.pushrules.ConditionResolver -import org.matrix.android.sdk.api.pushrules.rest.PushRule import org.matrix.android.sdk.api.session.events.model.Event +import org.matrix.android.sdk.api.session.pushrules.ConditionResolver +import org.matrix.android.sdk.api.session.pushrules.rest.PushRule import javax.inject.Inject internal class PushRuleFinder @Inject constructor( diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/DefaultRoom.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/DefaultRoom.kt index 3f129c4d71..7326adee4c 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/DefaultRoom.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/DefaultRoom.kt @@ -18,13 +18,11 @@ package org.matrix.android.sdk.internal.session.room import androidx.lifecycle.LiveData import org.matrix.android.sdk.api.MatrixCoroutineDispatchers -import org.matrix.android.sdk.api.crypto.MXCRYPTO_ALGORITHM_MEGOLM -import org.matrix.android.sdk.api.session.crypto.CryptoService -import org.matrix.android.sdk.api.session.events.model.EventType import org.matrix.android.sdk.api.session.room.Room import org.matrix.android.sdk.api.session.room.accountdata.RoomAccountDataService import org.matrix.android.sdk.api.session.room.alias.AliasService import org.matrix.android.sdk.api.session.room.call.RoomCallService +import org.matrix.android.sdk.api.session.room.crypto.RoomCryptoService import org.matrix.android.sdk.api.session.room.members.MembershipService import org.matrix.android.sdk.api.session.room.model.RoomSummary import org.matrix.android.sdk.api.session.room.model.RoomType @@ -42,62 +40,37 @@ import org.matrix.android.sdk.api.session.room.timeline.TimelineService import org.matrix.android.sdk.api.session.room.typing.TypingService import org.matrix.android.sdk.api.session.room.uploads.UploadsService import org.matrix.android.sdk.api.session.room.version.RoomVersionService -import org.matrix.android.sdk.api.session.search.SearchResult import org.matrix.android.sdk.api.session.space.Space import org.matrix.android.sdk.api.util.Optional -import org.matrix.android.sdk.api.util.awaitCallback import org.matrix.android.sdk.internal.session.permalinks.ViaParameterFinder -import org.matrix.android.sdk.internal.session.room.state.SendStateTask import org.matrix.android.sdk.internal.session.room.summary.RoomSummaryDataSource -import org.matrix.android.sdk.internal.session.search.SearchTask import org.matrix.android.sdk.internal.session.space.DefaultSpace -import java.security.InvalidParameterException -internal class DefaultRoom(override val roomId: String, - private val roomSummaryDataSource: RoomSummaryDataSource, - private val timelineService: TimelineService, - private val threadsService: ThreadsService, - private val threadsLocalService: ThreadsLocalService, - private val sendService: SendService, - private val draftService: DraftService, - private val stateService: StateService, - private val uploadsService: UploadsService, - private val reportingService: ReportingService, - private val roomCallService: RoomCallService, - private val readService: ReadService, - private val typingService: TypingService, - private val aliasService: AliasService, - private val tagsService: TagsService, - private val cryptoService: CryptoService, - private val relationService: RelationService, - private val roomMembersService: MembershipService, - private val roomPushRuleService: RoomPushRuleService, - private val roomAccountDataService: RoomAccountDataService, - private val roomVersionService: RoomVersionService, - private val sendStateTask: SendStateTask, - private val viaParameterFinder: ViaParameterFinder, - private val searchTask: SearchTask, - override val coroutineDispatchers: MatrixCoroutineDispatchers -) : - Room, - TimelineService by timelineService, - ThreadsService by threadsService, - ThreadsLocalService by threadsLocalService, - SendService by sendService, - DraftService by draftService, - StateService by stateService, - UploadsService by uploadsService, - ReportingService by reportingService, - RoomCallService by roomCallService, - ReadService by readService, - TypingService by typingService, - AliasService by aliasService, - TagsService by tagsService, - RelationService by relationService, - MembershipService by roomMembersService, - RoomPushRuleService by roomPushRuleService, - RoomAccountDataService by roomAccountDataService, - RoomVersionService by roomVersionService { +internal class DefaultRoom( + override val roomId: String, + private val roomSummaryDataSource: RoomSummaryDataSource, + private val roomCryptoService: RoomCryptoService, + private val timelineService: TimelineService, + private val threadsService: ThreadsService, + private val threadsLocalService: ThreadsLocalService, + private val sendService: SendService, + private val draftService: DraftService, + private val stateService: StateService, + private val uploadsService: UploadsService, + private val reportingService: ReportingService, + private val roomCallService: RoomCallService, + private val readService: ReadService, + private val typingService: TypingService, + private val aliasService: AliasService, + private val tagsService: TagsService, + private val relationService: RelationService, + private val roomMembersService: MembershipService, + private val roomPushRuleService: RoomPushRuleService, + private val roomAccountDataService: RoomAccountDataService, + private val roomVersionService: RoomVersionService, + private val viaParameterFinder: ViaParameterFinder, + override val coroutineDispatchers: MatrixCoroutineDispatchers +) : Room { override fun getRoomSummaryLive(): LiveData> { return roomSummaryDataSource.getRoomSummaryLive(roomId) @@ -107,69 +80,28 @@ internal class DefaultRoom(override val roomId: String, return roomSummaryDataSource.getRoomSummary(roomId) } - override fun isEncrypted(): Boolean { - return cryptoService.isRoomEncrypted(roomId) - } - - override fun encryptionAlgorithm(): String? { - return cryptoService.getEncryptionAlgorithm(roomId) - } - - override fun shouldEncryptForInvitedMembers(): Boolean { - return cryptoService.shouldEncryptForInvitedMembers(roomId) - } - - override suspend fun prepareToEncrypt() { - awaitCallback { - cryptoService.prepareToEncrypt(roomId, it) - } - } - - override suspend fun enableEncryption(algorithm: String, force: Boolean) { - when { - (!force && isEncrypted() && encryptionAlgorithm() == MXCRYPTO_ALGORITHM_MEGOLM) -> { - throw IllegalStateException("Encryption is already enabled for this room") - } - (!force && algorithm != MXCRYPTO_ALGORITHM_MEGOLM) -> { - throw InvalidParameterException("Only MXCRYPTO_ALGORITHM_MEGOLM algorithm is supported") - } - else -> { - val params = SendStateTask.Params( - roomId = roomId, - stateKey = "", - eventType = EventType.STATE_ROOM_ENCRYPTION, - body = mapOf( - "algorithm" to algorithm - )) - - sendStateTask.execute(params) - } - } - } - - override suspend fun search(searchTerm: String, - nextBatch: String?, - orderByRecent: Boolean, - limit: Int, - beforeLimit: Int, - afterLimit: Int, - includeProfile: Boolean): SearchResult { - return searchTask.execute( - SearchTask.Params( - searchTerm = searchTerm, - roomId = roomId, - nextBatch = nextBatch, - orderByRecent = orderByRecent, - limit = limit, - beforeLimit = beforeLimit, - afterLimit = afterLimit, - includeProfile = includeProfile - ) - ) - } - override fun asSpace(): Space? { if (roomSummary()?.roomType != RoomType.SPACE) return null return DefaultSpace(this, roomSummaryDataSource, viaParameterFinder) } + + override fun timelineService() = timelineService + override fun threadsService() = threadsService + override fun threadsLocalService() = threadsLocalService + override fun sendService() = sendService + override fun draftService() = draftService + override fun stateService() = stateService + override fun uploadsService() = uploadsService + override fun reportingService() = reportingService + override fun roomCallService() = roomCallService + override fun readService() = readService + override fun typingService() = typingService + override fun aliasService() = aliasService + override fun tagsService() = tagsService + override fun relationService() = relationService + override fun roomCryptoService() = roomCryptoService + override fun membershipService() = roomMembersService + override fun roomPushRuleService() = roomPushRuleService + override fun roomAccountDataService() = roomAccountDataService + override fun roomVersionService() = roomVersionService } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/EventRelationsAggregationProcessor.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/EventRelationsAggregationProcessor.kt index 15ce5810c8..16a63a9a96 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/EventRelationsAggregationProcessor.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/EventRelationsAggregationProcessor.kt @@ -28,14 +28,16 @@ import org.matrix.android.sdk.api.session.events.model.content.EncryptedEventCon import org.matrix.android.sdk.api.session.events.model.getRelationContent import org.matrix.android.sdk.api.session.events.model.toContent import org.matrix.android.sdk.api.session.events.model.toModel +import org.matrix.android.sdk.api.session.room.getTimelineEvent import org.matrix.android.sdk.api.session.room.model.PollSummaryContent import org.matrix.android.sdk.api.session.room.model.PowerLevelsContent import org.matrix.android.sdk.api.session.room.model.ReferencesAggregatedContent import org.matrix.android.sdk.api.session.room.model.VoteInfo import org.matrix.android.sdk.api.session.room.model.VoteSummary +import org.matrix.android.sdk.api.session.room.model.message.MessageBeaconInfoContent +import org.matrix.android.sdk.api.session.room.model.message.MessageBeaconLocationDataContent import org.matrix.android.sdk.api.session.room.model.message.MessageContent import org.matrix.android.sdk.api.session.room.model.message.MessageEndPollContent -import org.matrix.android.sdk.api.session.room.model.message.MessageLiveLocationContent import org.matrix.android.sdk.api.session.room.model.message.MessagePollContent import org.matrix.android.sdk.api.session.room.model.message.MessagePollResponseContent import org.matrix.android.sdk.api.session.room.model.message.MessageRelationContent @@ -67,6 +69,7 @@ import org.matrix.android.sdk.internal.di.UserId import org.matrix.android.sdk.internal.session.EventInsertLiveProcessor import org.matrix.android.sdk.internal.session.room.aggregation.livelocation.LiveLocationAggregationProcessor import org.matrix.android.sdk.internal.session.room.state.StateEventDataSource +import org.matrix.android.sdk.internal.util.time.Clock import timber.log.Timber import javax.inject.Inject @@ -75,7 +78,8 @@ internal class EventRelationsAggregationProcessor @Inject constructor( private val stateEventDataSource: StateEventDataSource, @SessionId private val sessionId: String, private val sessionManager: SessionManager, - private val liveLocationAggregationProcessor: LiveLocationAggregationProcessor + private val liveLocationAggregationProcessor: LiveLocationAggregationProcessor, + private val clock: Clock, ) : EventInsertLiveProcessor { private val allowedTypes = listOf( @@ -91,7 +95,7 @@ internal class EventRelationsAggregationProcessor @Inject constructor( // EventType.KEY_VERIFICATION_READY, EventType.KEY_VERIFICATION_KEY, EventType.ENCRYPTED - ) + EventType.POLL_START + EventType.POLL_RESPONSE + EventType.POLL_END + EventType.BEACON_LOCATION_DATA + ) + EventType.POLL_START + EventType.POLL_RESPONSE + EventType.POLL_END + EventType.STATE_ROOM_BEACON_INFO + EventType.BEACON_LOCATION_DATA override fun shouldProcess(eventId: String, eventType: String, insertType: EventInsertType): Boolean { return allowedTypes.contains(eventType) @@ -106,12 +110,12 @@ internal class EventRelationsAggregationProcessor @Inject constructor( } val isLocalEcho = LocalEcho.isLocalEchoId(event.eventId ?: "") when (event.type) { - EventType.REACTION -> { + EventType.REACTION -> { // we got a reaction!! Timber.v("###REACTION in room $roomId , reaction eventID ${event.eventId}") handleReaction(realm, event, roomId, isLocalEcho) } - EventType.MESSAGE -> { + EventType.MESSAGE -> { if (event.unsignedData?.relations?.annotations != null) { Timber.v("###REACTION Aggregation in room $roomId for event ${event.eventId}") handleInitialAggregatedRelations(realm, event, roomId, event.unsignedData.relations.annotations) @@ -137,7 +141,7 @@ internal class EventRelationsAggregationProcessor @Inject constructor( EventType.KEY_VERIFICATION_START, EventType.KEY_VERIFICATION_MAC, EventType.KEY_VERIFICATION_READY, - EventType.KEY_VERIFICATION_KEY -> { + EventType.KEY_VERIFICATION_KEY -> { Timber.v("## SAS REF in room $roomId for event ${event.eventId}") event.content.toModel()?.relatesTo?.let { if (it.type == RelationType.REFERENCE && it.eventId != null) { @@ -146,7 +150,7 @@ internal class EventRelationsAggregationProcessor @Inject constructor( } } - EventType.ENCRYPTED -> { + EventType.ENCRYPTED -> { // Relation type is in clear val encryptedEventContent = event.content.toModel() if (encryptedEventContent?.relatesTo?.type == RelationType.REPLACE || @@ -189,8 +193,8 @@ internal class EventRelationsAggregationProcessor @Inject constructor( } } in EventType.BEACON_LOCATION_DATA -> { - event.content.toModel(catchError = true)?.let { - liveLocationAggregationProcessor.handleLiveLocation(realm, event, it, roomId, isLocalEcho) + event.getClearContent().toModel(catchError = true)?.let { + liveLocationAggregationProcessor.handleBeaconLocationData(realm, event, it, roomId, isLocalEcho) } } } @@ -213,7 +217,7 @@ internal class EventRelationsAggregationProcessor @Inject constructor( // } // } } - EventType.REDACTION -> { + EventType.REDACTION -> { val eventToPrune = event.redacts?.let { EventEntity.where(realm, eventId = it).findFirst() } ?: return when (eventToPrune.type) { @@ -233,7 +237,7 @@ internal class EventRelationsAggregationProcessor @Inject constructor( } } } - in EventType.POLL_START -> { + in EventType.POLL_START -> { val content: MessagePollContent? = event.content.toModel() if (content?.relatesTo?.type == RelationType.REPLACE) { Timber.v("###REPLACE in room $roomId for event ${event.eventId}") @@ -241,22 +245,22 @@ internal class EventRelationsAggregationProcessor @Inject constructor( handleReplace(realm, event, content, roomId, isLocalEcho) } } - in EventType.POLL_RESPONSE -> { + in EventType.POLL_RESPONSE -> { event.content.toModel(catchError = true)?.let { handleResponse(realm, event, it, roomId, isLocalEcho) } } - in EventType.POLL_END -> { + in EventType.POLL_END -> { event.content.toModel(catchError = true)?.let { handleEndPoll(realm, event, it, roomId, isLocalEcho) } } - in EventType.BEACON_LOCATION_DATA -> { - event.content.toModel(catchError = true)?.let { - liveLocationAggregationProcessor.handleLiveLocation(realm, event, it, roomId, isLocalEcho) + in EventType.STATE_ROOM_BEACON_INFO -> { + event.content.toModel(catchError = true)?.let { + liveLocationAggregationProcessor.handleBeaconInfo(realm, event, it, roomId, isLocalEcho) } } - else -> Timber.v("UnHandled event ${event.eventId}") + else -> Timber.v("UnHandled event ${event.eventId}") } } catch (t: Throwable) { Timber.e(t, "## Should not happen ") @@ -325,7 +329,8 @@ internal class EventRelationsAggregationProcessor @Inject constructor( totalVotes = 0, winnerVoteCount = 0, ) - .toContent()) + .toContent() + ) } val txId = event.unsignedData?.transactionId @@ -335,7 +340,7 @@ internal class EventRelationsAggregationProcessor @Inject constructor( Timber.v("###REPLACE Receiving remote echo of edit (edit already done)") existingSummary.editions.firstOrNull { it.eventId == txId }?.let { it.eventId = event.eventId - it.timestamp = event.originServerTs ?: System.currentTimeMillis() + it.timestamp = event.originServerTs ?: clock.epochMillis() it.isLocalEcho = false } } else { @@ -346,10 +351,10 @@ internal class EventRelationsAggregationProcessor @Inject constructor( eventId = event.eventId, content = ContentMapper.map(newContent), timestamp = if (isLocalEcho) { - System.currentTimeMillis() + clock.epochMillis() } else { // Do not take local echo originServerTs here, could mess up ordering (keep old ts) - event.originServerTs ?: System.currentTimeMillis() + event.originServerTs ?: clock.epochMillis() }, isLocalEcho = isLocalEcho ) @@ -367,7 +372,7 @@ internal class EventRelationsAggregationProcessor @Inject constructor( } /** - * Check if the edition is on the latest thread event, and update it accordingly + * Check if the edition is on the latest thread event, and update it accordingly. * @param editedEvent The event that will be changed * @param replaceEvent The new event */ @@ -542,7 +547,7 @@ internal class EventRelationsAggregationProcessor @Inject constructor( private fun getPollEvent(roomId: String, eventId: String): TimelineEvent? { val session = sessionManager.getSessionComponent(sessionId)?.session() - return session?.getRoom(roomId)?.getTimelineEvent(eventId) ?: return null.also { + return session?.roomService()?.getRoom(roomId)?.getTimelineEvent(eventId) ?: return null.also { Timber.v("## POLL target poll event $eventId not found in room $roomId") } } @@ -646,7 +651,7 @@ internal class EventRelationsAggregationProcessor @Inject constructor( } /** - * Called when an event is deleted + * Called when an event is deleted. */ private fun handleRedactionOfReplace(realm: Realm, redacted: EventEntity, @@ -729,11 +734,13 @@ internal class EventRelationsAggregationProcessor @Inject constructor( EventType.KEY_VERIFICATION_READY, EventType.KEY_VERIFICATION_KEY, EventType.KEY_VERIFICATION_MAC -> currentState.toState(VerificationState.WAITING) - EventType.KEY_VERIFICATION_CANCEL -> currentState.toState(if (event.senderId == userId) { - VerificationState.CANCELED_BY_ME - } else { - VerificationState.CANCELED_BY_OTHER - }) + EventType.KEY_VERIFICATION_CANCEL -> currentState.toState( + if (event.senderId == userId) { + VerificationState.CANCELED_BY_ME + } else { + VerificationState.CANCELED_BY_OTHER + } + ) EventType.KEY_VERIFICATION_DONE -> currentState.toState(VerificationState.DONE) else -> VerificationState.REQUEST } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/RoomAPI.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/RoomAPI.kt index 10f75473b7..72f56ddf68 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/RoomAPI.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/RoomAPI.kt @@ -92,7 +92,7 @@ internal interface RoomAPI { ): PaginationResponse /** - * Get all members of a room + * Get all members of a room. * * @param roomId the room id where to get the members * @param syncToken the sync token (optional) @@ -136,7 +136,7 @@ internal interface RoomAPI { @Query("filter") filter: String? = null): EventContextResponse /** - * Retrieve an event from its room id / events id + * Retrieve an event from its room id / events id. * * @param roomId the room id * @param eventId the event Id @@ -156,7 +156,7 @@ internal interface RoomAPI { @Body markers: Map) /** - * Send receipt to a room + * Send receipt to a room. */ @POST(NetworkConstants.URI_API_PREFIX_PATH_R0 + "rooms/{roomId}/receipt/{receiptType}/{eventId}") suspend fun sendReceipt(@Path("roomId") roomId: String, @@ -185,7 +185,7 @@ internal interface RoomAPI { @Body body: ThreePidInviteBody) /** - * Send a generic state event + * Send a generic state event. * * @param roomId the room id. * @param stateEventType the state event type @@ -194,10 +194,11 @@ internal interface RoomAPI { @PUT(NetworkConstants.URI_API_PREFIX_PATH_R0 + "rooms/{roomId}/state/{state_event_type}") suspend fun sendStateEvent(@Path("roomId") roomId: String, @Path("state_event_type") stateEventType: String, - @Body params: JsonDict) + @Body params: JsonDict + ): SendResponse /** - * Send a generic state event + * Send a generic state event. * * @param roomId the room id. * @param stateEventType the state event type @@ -208,7 +209,8 @@ internal interface RoomAPI { suspend fun sendStateEvent(@Path("roomId") roomId: String, @Path("state_event_type") stateEventType: String, @Path("state_key") stateKey: String, - @Body params: JsonDict) + @Body params: JsonDict + ): SendResponse /** * Get state events of a room @@ -218,7 +220,7 @@ internal interface RoomAPI { suspend fun getRoomState(@Path("roomId") roomId: String): List /** - * Paginate relations for event based in normal topological order + * Paginate relations for event based in normal topological order. * @param relationType filter for this relation type * @param eventType filter for this event type */ @@ -233,7 +235,7 @@ internal interface RoomAPI { ): RelationsResponse /** - * Paginate relations for thread events based in normal topological order + * Paginate relations for thread events based in normal topological order. * @param relationType filter for this relation type */ @GET(NetworkConstants.URI_API_PREFIX_PATH_UNSTABLE + "rooms/{roomId}/relations/{eventId}/{relationType}") @@ -335,14 +337,14 @@ internal interface RoomAPI { suspend fun getAliases(@Path("roomId") roomId: String): GetAliasesResponse /** - * Inform that the user is starting to type or has stopped typing + * Inform that the user is starting to type or has stopped typing. */ @PUT(NetworkConstants.URI_API_PREFIX_PATH_R0 + "rooms/{roomId}/typing/{userId}") suspend fun sendTypingState(@Path("roomId") roomId: String, @Path("userId") userId: String, @Body body: TypingBody) - /** + /* * Room tagging */ diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/RoomAvatarResolver.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/RoomAvatarResolver.kt index 60ad83ee05..29a303475b 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/RoomAvatarResolver.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/RoomAvatarResolver.kt @@ -34,7 +34,7 @@ import javax.inject.Inject internal class RoomAvatarResolver @Inject constructor(@UserId private val userId: String) { /** - * Compute the room avatar url + * Compute the room avatar url. * @param realm: the current instance of realm * @param roomId the roomId of the room to resolve avatar * @return the room avatar url, can be a fallback to a room member avatar or null diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/RoomFactory.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/RoomFactory.kt index 72a3f9ab22..01c4fd1501 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/RoomFactory.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/RoomFactory.kt @@ -17,13 +17,13 @@ package org.matrix.android.sdk.internal.session.room import org.matrix.android.sdk.api.MatrixCoroutineDispatchers -import org.matrix.android.sdk.api.session.crypto.CryptoService import org.matrix.android.sdk.api.session.room.Room import org.matrix.android.sdk.internal.session.SessionScope import org.matrix.android.sdk.internal.session.permalinks.ViaParameterFinder import org.matrix.android.sdk.internal.session.room.accountdata.DefaultRoomAccountDataService import org.matrix.android.sdk.internal.session.room.alias.DefaultAliasService import org.matrix.android.sdk.internal.session.room.call.DefaultRoomCallService +import org.matrix.android.sdk.internal.session.room.crypto.DefaultRoomCryptoService import org.matrix.android.sdk.internal.session.room.draft.DefaultDraftService import org.matrix.android.sdk.internal.session.room.membership.DefaultMembershipService import org.matrix.android.sdk.internal.session.room.notification.DefaultRoomPushRuleService @@ -32,7 +32,6 @@ import org.matrix.android.sdk.internal.session.room.relation.DefaultRelationServ import org.matrix.android.sdk.internal.session.room.reporting.DefaultReportingService import org.matrix.android.sdk.internal.session.room.send.DefaultSendService import org.matrix.android.sdk.internal.session.room.state.DefaultStateService -import org.matrix.android.sdk.internal.session.room.state.SendStateTask import org.matrix.android.sdk.internal.session.room.summary.RoomSummaryDataSource import org.matrix.android.sdk.internal.session.room.tags.DefaultTagsService import org.matrix.android.sdk.internal.session.room.threads.DefaultThreadsService @@ -41,7 +40,6 @@ import org.matrix.android.sdk.internal.session.room.timeline.DefaultTimelineServ import org.matrix.android.sdk.internal.session.room.typing.DefaultTypingService import org.matrix.android.sdk.internal.session.room.uploads.DefaultUploadsService import org.matrix.android.sdk.internal.session.room.version.DefaultRoomVersionService -import org.matrix.android.sdk.internal.session.search.SearchTask import javax.inject.Inject internal interface RoomFactory { @@ -49,36 +47,36 @@ internal interface RoomFactory { } @SessionScope -internal class DefaultRoomFactory @Inject constructor(private val cryptoService: CryptoService, - private val roomSummaryDataSource: RoomSummaryDataSource, - private val timelineServiceFactory: DefaultTimelineService.Factory, - private val threadsServiceFactory: DefaultThreadsService.Factory, - private val threadsLocalServiceFactory: DefaultThreadsLocalService.Factory, - private val sendServiceFactory: DefaultSendService.Factory, - private val draftServiceFactory: DefaultDraftService.Factory, - private val stateServiceFactory: DefaultStateService.Factory, - private val uploadsServiceFactory: DefaultUploadsService.Factory, - private val reportingServiceFactory: DefaultReportingService.Factory, - private val roomCallServiceFactory: DefaultRoomCallService.Factory, - private val readServiceFactory: DefaultReadService.Factory, - private val typingServiceFactory: DefaultTypingService.Factory, - private val aliasServiceFactory: DefaultAliasService.Factory, - private val tagsServiceFactory: DefaultTagsService.Factory, - private val relationServiceFactory: DefaultRelationService.Factory, - private val membershipServiceFactory: DefaultMembershipService.Factory, - private val roomPushRuleServiceFactory: DefaultRoomPushRuleService.Factory, - private val roomVersionServiceFactory: DefaultRoomVersionService.Factory, - private val roomAccountDataServiceFactory: DefaultRoomAccountDataService.Factory, - private val sendStateTask: SendStateTask, - private val viaParameterFinder: ViaParameterFinder, - private val searchTask: SearchTask, - private val coroutineDispatchers: MatrixCoroutineDispatchers) : - RoomFactory { +internal class DefaultRoomFactory @Inject constructor( + private val roomSummaryDataSource: RoomSummaryDataSource, + private val timelineServiceFactory: DefaultTimelineService.Factory, + private val threadsServiceFactory: DefaultThreadsService.Factory, + private val threadsLocalServiceFactory: DefaultThreadsLocalService.Factory, + private val sendServiceFactory: DefaultSendService.Factory, + private val draftServiceFactory: DefaultDraftService.Factory, + private val stateServiceFactory: DefaultStateService.Factory, + private val uploadsServiceFactory: DefaultUploadsService.Factory, + private val reportingServiceFactory: DefaultReportingService.Factory, + private val roomCallServiceFactory: DefaultRoomCallService.Factory, + private val readServiceFactory: DefaultReadService.Factory, + private val typingServiceFactory: DefaultTypingService.Factory, + private val aliasServiceFactory: DefaultAliasService.Factory, + private val tagsServiceFactory: DefaultTagsService.Factory, + private val relationServiceFactory: DefaultRelationService.Factory, + private val roomCryptoServiceFactory: DefaultRoomCryptoService.Factory, + private val membershipServiceFactory: DefaultMembershipService.Factory, + private val roomPushRuleServiceFactory: DefaultRoomPushRuleService.Factory, + private val roomVersionServiceFactory: DefaultRoomVersionService.Factory, + private val roomAccountDataServiceFactory: DefaultRoomAccountDataService.Factory, + private val viaParameterFinder: ViaParameterFinder, + private val coroutineDispatchers: MatrixCoroutineDispatchers +) : RoomFactory { override fun create(roomId: String): Room { return DefaultRoom( roomId = roomId, roomSummaryDataSource = roomSummaryDataSource, + roomCryptoService = roomCryptoServiceFactory.create(roomId), timelineService = timelineServiceFactory.create(roomId), threadsService = threadsServiceFactory.create(roomId), threadsLocalService = threadsLocalServiceFactory.create(roomId), @@ -92,14 +90,11 @@ internal class DefaultRoomFactory @Inject constructor(private val cryptoService: typingService = typingServiceFactory.create(roomId), aliasService = aliasServiceFactory.create(roomId), tagsService = tagsServiceFactory.create(roomId), - cryptoService = cryptoService, relationService = relationServiceFactory.create(roomId), roomMembersService = membershipServiceFactory.create(roomId), roomPushRuleService = roomPushRuleServiceFactory.create(roomId), roomAccountDataService = roomAccountDataServiceFactory.create(roomId), roomVersionService = roomVersionServiceFactory.create(roomId), - sendStateTask = sendStateTask, - searchTask = searchTask, viaParameterFinder = viaParameterFinder, coroutineDispatchers = coroutineDispatchers ) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/RoomModule.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/RoomModule.kt index 5e90076b8a..f3845f1f15 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/RoomModule.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/RoomModule.kt @@ -108,14 +108,14 @@ import retrofit2.Retrofit import javax.inject.Qualifier /** - * Used to inject the simple commonmark Parser + * Used to inject the simple commonmark Parser. */ @Qualifier @Retention(AnnotationRetention.RUNTIME) internal annotation class SimpleCommonmarkParser /** - * Used to inject the advanced commonmark Parser + * Used to inject the advanced commonmark Parser. */ @Qualifier @Retention(AnnotationRetention.RUNTIME) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/aggregation/livelocation/DefaultLiveLocationAggregationProcessor.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/aggregation/livelocation/DefaultLiveLocationAggregationProcessor.kt index 95e196c762..997e31a109 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/aggregation/livelocation/DefaultLiveLocationAggregationProcessor.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/aggregation/livelocation/DefaultLiveLocationAggregationProcessor.kt @@ -17,69 +17,78 @@ package org.matrix.android.sdk.internal.session.room.aggregation.livelocation import io.realm.Realm -import org.matrix.android.sdk.api.extensions.orFalse +import org.matrix.android.sdk.api.extensions.orTrue import org.matrix.android.sdk.api.session.events.model.Event -import org.matrix.android.sdk.api.session.events.model.EventType import org.matrix.android.sdk.api.session.events.model.toContent import org.matrix.android.sdk.api.session.events.model.toModel -import org.matrix.android.sdk.api.session.room.model.livelocation.LiveLocationBeaconContent -import org.matrix.android.sdk.api.session.room.model.message.MessageLiveLocationContent +import org.matrix.android.sdk.api.session.room.model.message.MessageBeaconInfoContent +import org.matrix.android.sdk.api.session.room.model.message.MessageBeaconLocationDataContent import org.matrix.android.sdk.internal.database.mapper.ContentMapper -import org.matrix.android.sdk.internal.database.model.CurrentStateEventEntity -import org.matrix.android.sdk.internal.database.query.getOrNull +import org.matrix.android.sdk.internal.database.model.livelocation.LiveLocationShareAggregatedSummaryEntity +import org.matrix.android.sdk.internal.database.query.getOrCreate import timber.log.Timber import javax.inject.Inject internal class DefaultLiveLocationAggregationProcessor @Inject constructor() : LiveLocationAggregationProcessor { - override fun handleLiveLocation(realm: Realm, event: Event, content: MessageLiveLocationContent, roomId: String, isLocalEcho: Boolean) { - val locationSenderId = event.senderId ?: return - - // We shouldn't process local echos - if (isLocalEcho) { + override fun handleBeaconInfo(realm: Realm, event: Event, content: MessageBeaconInfoContent, roomId: String, isLocalEcho: Boolean) { + if (event.senderId.isNullOrEmpty() || isLocalEcho) { return } - // A beacon info state event has to be sent before sending location - // TODO handle missing check of m_relatesTo field - var beaconInfoEntity: CurrentStateEventEntity? = null - val eventTypesIterator = EventType.STATE_ROOM_BEACON_INFO.iterator() - while (beaconInfoEntity == null && eventTypesIterator.hasNext()) { - beaconInfoEntity = CurrentStateEventEntity.getOrNull(realm, roomId, locationSenderId, eventTypesIterator.next()) - } - - if (beaconInfoEntity == null) { - Timber.v("## LIVE LOCATION. There is not any beacon info which should be emitted before sending location updates") - return - } - val beaconInfoContent = ContentMapper.map(beaconInfoEntity.root?.content)?.toModel(catchError = true) - if (beaconInfoContent == null) { - Timber.v("## LIVE LOCATION. Beacon info content is invalid") - return - } - - // Check if live location is ended - if (!beaconInfoContent.getBestBeaconInfo()?.isLive.orFalse()) { - Timber.v("## LIVE LOCATION. Beacon info is not live anymore") - return - } - - // Check if beacon info is outdated - if (isBeaconInfoOutdated(beaconInfoContent, content)) { - Timber.v("## LIVE LOCATION. Beacon info has timeout") - beaconInfoContent.hasTimedOut = true + val targetEventId = if (content.isLive.orTrue()) { + event.eventId } else { - beaconInfoContent.lastLocationContent = content + // when live is set to false, we use the id of the event that should have been replaced + event.unsignedData?.replacesState } - beaconInfoEntity.root?.content = ContentMapper.map(beaconInfoContent.toContent()) + if (targetEventId.isNullOrEmpty()) { + Timber.w("no target event id found for the beacon content") + return + } + + val aggregatedSummary = LiveLocationShareAggregatedSummaryEntity.getOrCreate( + realm = realm, + roomId = roomId, + eventId = targetEventId + ) + + Timber.d("updating summary of id=$targetEventId with isLive=${content.isLive}") + + aggregatedSummary.endOfLiveTimestampMillis = content.getBestTimestampMillis()?.let { it + (content.timeout ?: 0) } + aggregatedSummary.isActive = content.isLive } - private fun isBeaconInfoOutdated(beaconInfoContent: LiveLocationBeaconContent, - liveLocationContent: MessageLiveLocationContent): Boolean { - val beaconInfoStartTime = beaconInfoContent.getBestTimestampAsMilliseconds() ?: 0 - val liveLocationEventTime = liveLocationContent.getBestTimestampAsMilliseconds() ?: 0 - val timeout = beaconInfoContent.getBestBeaconInfo()?.timeout ?: 0 - return liveLocationEventTime - beaconInfoStartTime > timeout + override fun handleBeaconLocationData(realm: Realm, event: Event, content: MessageBeaconLocationDataContent, roomId: String, isLocalEcho: Boolean) { + if (event.senderId.isNullOrEmpty() || isLocalEcho) { + return + } + + val targetEventId = content.relatesTo?.eventId + + if (targetEventId.isNullOrEmpty()) { + Timber.w("no target event id found for the live location content") + return + } + + val aggregatedSummary = LiveLocationShareAggregatedSummaryEntity.getOrCreate( + realm = realm, + roomId = roomId, + eventId = targetEventId + ) + val updatedLocationTimestamp = content.getBestTimestampMillis() ?: 0 + val currentLocationTimestamp = ContentMapper + .map(aggregatedSummary.lastLocationContent) + .toModel() + ?.getBestTimestampMillis() + ?: 0 + + if (updatedLocationTimestamp.isMoreRecentThan(currentLocationTimestamp)) { + Timber.d("updating last location of the summary of id=$targetEventId") + aggregatedSummary.lastLocationContent = ContentMapper.map(content.toContent()) + } } + + private fun Long.isMoreRecentThan(timestamp: Long) = this > timestamp } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/aggregation/livelocation/LiveLocationAggregationProcessor.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/aggregation/livelocation/LiveLocationAggregationProcessor.kt index 7b5f23e243..c0be96f83d 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/aggregation/livelocation/LiveLocationAggregationProcessor.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/aggregation/livelocation/LiveLocationAggregationProcessor.kt @@ -18,12 +18,23 @@ package org.matrix.android.sdk.internal.session.room.aggregation.livelocation import io.realm.Realm import org.matrix.android.sdk.api.session.events.model.Event -import org.matrix.android.sdk.api.session.room.model.message.MessageLiveLocationContent +import org.matrix.android.sdk.api.session.room.model.message.MessageBeaconInfoContent +import org.matrix.android.sdk.api.session.room.model.message.MessageBeaconLocationDataContent internal interface LiveLocationAggregationProcessor { - fun handleLiveLocation(realm: Realm, - event: Event, - content: MessageLiveLocationContent, - roomId: String, - isLocalEcho: Boolean) + fun handleBeaconInfo( + realm: Realm, + event: Event, + content: MessageBeaconInfoContent, + roomId: String, + isLocalEcho: Boolean, + ) + + fun handleBeaconLocationData( + realm: Realm, + event: Event, + content: MessageBeaconLocationDataContent, + roomId: String, + isLocalEcho: Boolean, + ) } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/alias/GetRoomIdByAliasTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/alias/GetRoomIdByAliasTask.kt index dc3ea55a01..b25ef7ba0f 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/alias/GetRoomIdByAliasTask.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/alias/GetRoomIdByAliasTask.kt @@ -52,7 +52,7 @@ internal class DefaultGetRoomIdByAliasTask @Inject constructor( } else if (!params.searchOnServer) { Optional.from(null) } else { - val description = tryOrNull("## Failed to get roomId from alias") { + val description = tryOrNull("## Failed to get roomId from alias") { executeRequest(globalErrorReceiver) { directoryAPI.getRoomIdByAlias(params.roomAlias) } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/create/CreateRoomBody.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/create/CreateRoomBody.kt index 69352688e3..cffa632768 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/create/CreateRoomBody.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/create/CreateRoomBody.kt @@ -25,7 +25,7 @@ import org.matrix.android.sdk.api.session.room.model.create.CreateRoomPreset import org.matrix.android.sdk.internal.session.room.membership.threepid.ThreePidInviteBody /** - * Parameter to create a room + * Parameter to create a room. */ @JsonClass(generateAdapter = true) internal data class CreateRoomBody( @@ -108,7 +108,7 @@ internal data class CreateRoomBody( val isDirect: Boolean?, /** - * The power level content to override in the default power level event + * The power level content to override in the default power level event. */ @Json(name = "power_level_content_override") val powerLevelContentOverride: PowerLevelsContent?, diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/create/CreateRoomBodyBuilder.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/create/CreateRoomBodyBuilder.kt index 3b2e9d3d22..faf68d538f 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/create/CreateRoomBodyBuilder.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/create/CreateRoomBodyBuilder.kt @@ -116,7 +116,8 @@ internal class CreateRoomBodyBuilder @Inject constructor( fileUploader.uploadFromUri( uri = avatarUri, filename = UUID.randomUUID().toString(), - mimeType = MimeTypes.Jpeg) + mimeType = MimeTypes.Jpeg + ) } }?.let { response -> Event( diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/create/CreateRoomTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/create/CreateRoomTask.kt index 9bd15a0267..6dd2c91048 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/create/CreateRoomTask.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/create/CreateRoomTask.kt @@ -41,6 +41,7 @@ import org.matrix.android.sdk.internal.session.user.accountdata.DirectChatsHelpe import org.matrix.android.sdk.internal.session.user.accountdata.UpdateUserAccountDataTask import org.matrix.android.sdk.internal.task.Task import org.matrix.android.sdk.internal.util.awaitTransaction +import org.matrix.android.sdk.internal.util.time.Clock import java.util.concurrent.TimeUnit import javax.inject.Inject @@ -56,7 +57,8 @@ internal class DefaultCreateRoomTask @Inject constructor( @SessionDatabase private val realmConfiguration: RealmConfiguration, private val createRoomBodyBuilder: CreateRoomBodyBuilder, - private val globalErrorReceiver: GlobalErrorReceiver + private val globalErrorReceiver: GlobalErrorReceiver, + private val clock: Clock, ) : CreateRoomTask { override suspend fun execute(params: CreateRoomParams): String { @@ -106,7 +108,7 @@ internal class DefaultCreateRoomTask @Inject constructor( } awaitTransaction(realmConfiguration) { - RoomSummaryEntity.where(it, roomId).findFirst()?.lastActivityTime = System.currentTimeMillis() + RoomSummaryEntity.where(it, roomId).findFirst()?.lastActivityTime = clock.epochMillis() } if (otherUserId != null) { diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/crypto/DefaultRoomCryptoService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/crypto/DefaultRoomCryptoService.kt new file mode 100644 index 0000000000..2546c58cc7 --- /dev/null +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/crypto/DefaultRoomCryptoService.kt @@ -0,0 +1,81 @@ +/* + * Copyright (c) 2022 The Matrix.org Foundation C.I.C. + * + * 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 org.matrix.android.sdk.internal.session.room.crypto + +import dagger.assisted.Assisted +import dagger.assisted.AssistedFactory +import dagger.assisted.AssistedInject +import org.matrix.android.sdk.api.crypto.MXCRYPTO_ALGORITHM_MEGOLM +import org.matrix.android.sdk.api.session.crypto.CryptoService +import org.matrix.android.sdk.api.session.events.model.EventType +import org.matrix.android.sdk.api.session.room.crypto.RoomCryptoService +import org.matrix.android.sdk.api.util.awaitCallback +import org.matrix.android.sdk.internal.session.room.state.SendStateTask +import java.security.InvalidParameterException + +internal class DefaultRoomCryptoService @AssistedInject constructor( + @Assisted private val roomId: String, + private val cryptoService: CryptoService, + private val sendStateTask: SendStateTask, +) : RoomCryptoService { + + @AssistedFactory + interface Factory { + fun create(roomId: String): DefaultRoomCryptoService + } + + override fun isEncrypted(): Boolean { + return cryptoService.isRoomEncrypted(roomId) + } + + override fun encryptionAlgorithm(): String? { + return cryptoService.getEncryptionAlgorithm(roomId) + } + + override fun shouldEncryptForInvitedMembers(): Boolean { + return cryptoService.shouldEncryptForInvitedMembers(roomId) + } + + override suspend fun prepareToEncrypt() { + awaitCallback { + cryptoService.prepareToEncrypt(roomId, it) + } + } + + override suspend fun enableEncryption(algorithm: String, force: Boolean) { + when { + (!force && isEncrypted() && encryptionAlgorithm() == MXCRYPTO_ALGORITHM_MEGOLM) -> { + throw IllegalStateException("Encryption is already enabled for this room") + } + (!force && algorithm != MXCRYPTO_ALGORITHM_MEGOLM) -> { + throw InvalidParameterException("Only MXCRYPTO_ALGORITHM_MEGOLM algorithm is supported") + } + else -> { + val params = SendStateTask.Params( + roomId = roomId, + stateKey = "", + eventType = EventType.STATE_ROOM_ENCRYPTION, + body = mapOf( + "algorithm" to algorithm + ) + ) + + sendStateTask.execute(params) + } + } + } +} diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/membership/LoadRoomMembersTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/membership/LoadRoomMembersTask.kt index 70ba9287a2..d3d1cb856a 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/membership/LoadRoomMembersTask.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/membership/LoadRoomMembersTask.kt @@ -42,6 +42,7 @@ import org.matrix.android.sdk.internal.session.room.summary.RoomSummaryUpdater import org.matrix.android.sdk.internal.session.sync.SyncTokenStore import org.matrix.android.sdk.internal.task.Task import org.matrix.android.sdk.internal.util.awaitTransaction +import org.matrix.android.sdk.internal.util.time.Clock import java.util.concurrent.TimeUnit import javax.inject.Inject @@ -61,7 +62,8 @@ internal class DefaultLoadRoomMembersTask @Inject constructor( private val roomMemberEventHandler: RoomMemberEventHandler, private val cryptoSessionInfoProvider: CryptoSessionInfoProvider, private val deviceListManager: DeviceListManager, - private val globalErrorReceiver: GlobalErrorReceiver + private val globalErrorReceiver: GlobalErrorReceiver, + private val clock: Clock, ) : LoadRoomMembersTask { override suspend fun execute(params: LoadRoomMembersTask.Params) { @@ -107,7 +109,7 @@ internal class DefaultLoadRoomMembersTask @Inject constructor( // We ignore all the already known members val roomEntity = RoomEntity.where(realm, roomId).findFirst() ?: realm.createObject(roomId) - val now = System.currentTimeMillis() + val now = clock.epochMillis() for (roomMemberEvent in response.roomMemberEvents) { if (roomMemberEvent.eventId == null || roomMemberEvent.stateKey == null || roomMemberEvent.type == null) { continue diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/membership/RoomDisplayNameResolver.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/membership/RoomDisplayNameResolver.kt index bd9f2ecc36..59e0f81ece 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/membership/RoomDisplayNameResolver.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/membership/RoomDisplayNameResolver.kt @@ -38,7 +38,7 @@ import org.matrix.android.sdk.internal.util.Normalizer import javax.inject.Inject /** - * This class computes room display name + * This class computes room display name. */ internal class RoomDisplayNameResolver @Inject constructor( matrixConfiguration: MatrixConfiguration, @@ -50,7 +50,7 @@ internal class RoomDisplayNameResolver @Inject constructor( private val roomDisplayNameFallbackProvider = matrixConfiguration.roomDisplayNameFallbackProvider /** - * Compute the room display name + * Compute the room display name. * * @param realm: the current instance of realm * @param roomId: the roomId to resolve the name of. @@ -157,7 +157,7 @@ internal class RoomDisplayNameResolver @Inject constructor( return (name ?: roomId).toRoomName() } - /** See [org.matrix.android.sdk.api.session.room.sender.SenderInfo.disambiguatedDisplayName] */ + /** See [org.matrix.android.sdk.api.session.room.sender.SenderInfo.disambiguatedDisplayName]. */ private fun resolveRoomMemberName(roomMemberSummary: RoomMemberSummaryEntity, roomMemberHelper: RoomMemberHelper): String { val isUnique = roomMemberHelper.isUniqueDisplayName(roomMemberSummary.displayName) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/membership/RoomMemberEventHandler.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/membership/RoomMemberEventHandler.kt index 85300fa351..a1b30a0be5 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/membership/RoomMemberEventHandler.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/membership/RoomMemberEventHandler.kt @@ -83,7 +83,8 @@ internal class RoomMemberEventHandler @Inject constructor( roomMember, // When an update is happening, insertOrUpdate replace existing values with null if they are not provided, // but we want to preserve presence record value and not replace it with null - getExistingPresenceState(realm, roomId, userId)) + getExistingPresenceState(realm, roomId, userId) + ) realm.insertOrUpdate(roomMemberEntity) } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/membership/RoomMemberHelper.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/membership/RoomMemberHelper.kt index 9ce8db25a5..40228fe8c9 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/membership/RoomMemberHelper.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/membership/RoomMemberHelper.kt @@ -104,7 +104,7 @@ internal class RoomMemberHelper(private val realm: Realm, } /** - * Return all the roomMembers ids which are joined or invited to the room + * Return all the roomMembers ids which are joined or invited to the room. * * @return a roomMember id list of joined or invited members. */ @@ -113,7 +113,7 @@ internal class RoomMemberHelper(private val realm: Realm, } /** - * Return all the roomMembers ids which are joined to the room + * Return all the roomMembers ids which are joined to the room. * * @return a roomMember id list of joined members. */ diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/membership/joining/JoinRoomTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/membership/joining/JoinRoomTask.kt index f883cc33ec..4c631c2182 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/membership/joining/JoinRoomTask.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/membership/joining/JoinRoomTask.kt @@ -18,6 +18,7 @@ package org.matrix.android.sdk.internal.session.room.membership.joining import io.realm.RealmConfiguration import kotlinx.coroutines.TimeoutCancellationException +import org.matrix.android.sdk.api.MatrixCoroutineDispatchers import org.matrix.android.sdk.api.session.events.model.toContent import org.matrix.android.sdk.api.session.identity.model.SignInvitationResult import org.matrix.android.sdk.api.session.room.failure.JoinRoomFailure @@ -35,6 +36,7 @@ import org.matrix.android.sdk.internal.session.room.RoomAPI import org.matrix.android.sdk.internal.session.room.membership.RoomChangeMembershipStateDataSource import org.matrix.android.sdk.internal.session.room.read.SetReadMarkersTask import org.matrix.android.sdk.internal.task.Task +import org.matrix.android.sdk.internal.util.time.Clock import java.util.concurrent.TimeUnit import javax.inject.Inject @@ -52,8 +54,10 @@ internal class DefaultJoinRoomTask @Inject constructor( private val readMarkersTask: SetReadMarkersTask, @SessionDatabase private val realmConfiguration: RealmConfiguration, + private val coroutineDispatcher: MatrixCoroutineDispatchers, private val roomChangeMembershipStateDataSource: RoomChangeMembershipStateDataSource, - private val globalErrorReceiver: GlobalErrorReceiver + private val globalErrorReceiver: GlobalErrorReceiver, + private val clock: Clock, ) : JoinRoomTask { override suspend fun execute(params: JoinRoomTask.Params) { @@ -90,7 +94,7 @@ internal class DefaultJoinRoomTask @Inject constructor( throw JoinRoomFailure.JoinedWithTimeout } awaitTransaction(realmConfiguration) { - RoomSummaryEntity.where(it, roomId).findFirst()?.lastActivityTime = System.currentTimeMillis() + RoomSummaryEntity.where(it, roomId).findFirst()?.lastActivityTime = clock.epochMillis() } setReadMarkers(roomId) } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/notification/DefaultRoomPushRuleService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/notification/DefaultRoomPushRuleService.kt index 8f1aefb731..85f53e1346 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/notification/DefaultRoomPushRuleService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/notification/DefaultRoomPushRuleService.kt @@ -22,7 +22,7 @@ import com.zhuinden.monarchy.Monarchy import dagger.assisted.Assisted import dagger.assisted.AssistedFactory import dagger.assisted.AssistedInject -import org.matrix.android.sdk.api.pushrules.RuleScope +import org.matrix.android.sdk.api.session.pushrules.RuleScope import org.matrix.android.sdk.api.session.room.notification.RoomNotificationState import org.matrix.android.sdk.api.session.room.notification.RoomPushRuleService import org.matrix.android.sdk.internal.database.model.PushRuleEntity @@ -32,7 +32,7 @@ import org.matrix.android.sdk.internal.di.SessionDatabase internal class DefaultRoomPushRuleService @AssistedInject constructor(@Assisted private val roomId: String, private val setRoomNotificationStateTask: SetRoomNotificationStateTask, @SessionDatabase private val monarchy: Monarchy) : - RoomPushRuleService { + RoomPushRuleService { @AssistedFactory interface Factory { diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/notification/RoomPushRule.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/notification/RoomPushRule.kt index d2c0d13b51..2c74e2a1e5 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/notification/RoomPushRule.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/notification/RoomPushRule.kt @@ -16,8 +16,8 @@ package org.matrix.android.sdk.internal.session.room.notification -import org.matrix.android.sdk.api.pushrules.RuleKind -import org.matrix.android.sdk.api.pushrules.rest.PushRule +import org.matrix.android.sdk.api.session.pushrules.RuleKind +import org.matrix.android.sdk.api.session.pushrules.rest.PushRule internal data class RoomPushRule( val kind: RuleKind, diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/notification/RoomPushRuleMapper.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/notification/RoomPushRuleMapper.kt index 86d2e6c619..a5a5ab58ba 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/notification/RoomPushRuleMapper.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/notification/RoomPushRuleMapper.kt @@ -16,13 +16,13 @@ package org.matrix.android.sdk.internal.session.room.notification -import org.matrix.android.sdk.api.pushrules.Action -import org.matrix.android.sdk.api.pushrules.Kind -import org.matrix.android.sdk.api.pushrules.RuleSetKey -import org.matrix.android.sdk.api.pushrules.getActions -import org.matrix.android.sdk.api.pushrules.rest.PushCondition -import org.matrix.android.sdk.api.pushrules.rest.PushRule -import org.matrix.android.sdk.api.pushrules.toJson +import org.matrix.android.sdk.api.session.pushrules.Action +import org.matrix.android.sdk.api.session.pushrules.Kind +import org.matrix.android.sdk.api.session.pushrules.RuleSetKey +import org.matrix.android.sdk.api.session.pushrules.getActions +import org.matrix.android.sdk.api.session.pushrules.rest.PushCondition +import org.matrix.android.sdk.api.session.pushrules.rest.PushRule +import org.matrix.android.sdk.api.session.pushrules.toJson import org.matrix.android.sdk.api.session.room.notification.RoomNotificationState import org.matrix.android.sdk.internal.database.mapper.PushRulesMapper import org.matrix.android.sdk.internal.database.model.PushRuleEntity diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/notification/SetRoomNotificationStateTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/notification/SetRoomNotificationStateTask.kt index feb8c27b09..021d7dbefb 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/notification/SetRoomNotificationStateTask.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/notification/SetRoomNotificationStateTask.kt @@ -18,7 +18,7 @@ package org.matrix.android.sdk.internal.session.room.notification import com.zhuinden.monarchy.Monarchy import io.realm.Realm -import org.matrix.android.sdk.api.pushrules.RuleScope +import org.matrix.android.sdk.api.session.pushrules.RuleScope import org.matrix.android.sdk.api.session.room.notification.RoomNotificationState import org.matrix.android.sdk.internal.database.model.PushRuleEntity import org.matrix.android.sdk.internal.database.query.where @@ -38,7 +38,7 @@ internal interface SetRoomNotificationStateTask : Task listOf("membership") EventType.STATE_ROOM_CREATE -> listOf("creator") EventType.STATE_ROOM_JOIN_RULES -> listOf("join_rule") - EventType.STATE_ROOM_POWER_LEVELS -> listOf("users", + EventType.STATE_ROOM_POWER_LEVELS -> listOf( + "users", "users_default", "events", "events_default", @@ -156,7 +157,8 @@ internal class RedactionEventProcessor @Inject constructor() : EventInsertLivePr "ban", "kick", "redact", - "invite") + "invite" + ) EventType.STATE_ROOM_ALIASES -> listOf("aliases") EventType.STATE_ROOM_CANONICAL_ALIAS -> listOf("alias") EventType.FEEDBACK -> listOf("type", "target_event_id") diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/read/SetReadMarkersTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/read/SetReadMarkersTask.kt index 64f1cc34f1..a124a8a4c2 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/read/SetReadMarkersTask.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/read/SetReadMarkersTask.kt @@ -34,6 +34,7 @@ import org.matrix.android.sdk.internal.session.sync.handler.room.ReadReceiptHand import org.matrix.android.sdk.internal.session.sync.handler.room.RoomFullyReadHandler import org.matrix.android.sdk.internal.task.Task import org.matrix.android.sdk.internal.util.awaitTransaction +import org.matrix.android.sdk.internal.util.time.Clock import timber.log.Timber import javax.inject.Inject import kotlin.collections.set @@ -58,7 +59,8 @@ internal class DefaultSetReadMarkersTask @Inject constructor( private val roomFullyReadHandler: RoomFullyReadHandler, private val readReceiptHandler: ReadReceiptHandler, @UserId private val userId: String, - private val globalErrorReceiver: GlobalErrorReceiver + private val globalErrorReceiver: GlobalErrorReceiver, + private val clock: Clock, ) : SetReadMarkersTask { override suspend fun execute(params: SetReadMarkersTask.Params) { @@ -125,7 +127,7 @@ internal class DefaultSetReadMarkersTask @Inject constructor( roomFullyReadHandler.handle(realm, roomId, FullyReadContent(readMarkerId)) } if (readReceiptId != null) { - val readReceiptContent = ReadReceiptHandler.createContent(userId, readReceiptId) + val readReceiptContent = ReadReceiptHandler.createContent(userId, readReceiptId, clock.epochMillis()) readReceiptHandler.handle(realm, roomId, readReceiptContent, false, null) } if (shouldUpdateRoomSummary) { diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/relation/DefaultRelationService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/relation/DefaultRelationService.kt index ab514d31c8..7b68e2a74c 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/relation/DefaultRelationService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/relation/DefaultRelationService.kt @@ -131,7 +131,8 @@ internal class DefaultRelationService @AssistedInject constructor( replyText = replyText, autoMarkdown = autoMarkdown, rootThreadEventId = rootThreadEventId, - showInThread = showInThread) + showInThread = showInThread + ) ?.also { saveLocalEcho(it) } ?: return null @@ -186,7 +187,8 @@ internal class DefaultRelationService @AssistedInject constructor( text = replyInThreadText, msgType = msgType, autoMarkdown = autoMarkdown, - formattedText = formattedText) + formattedText = formattedText + ) .also { saveLocalEcho(it) } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/relation/EventEditor.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/relation/EventEditor.kt index b54cd71e50..7bf7d6b587 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/relation/EventEditor.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/relation/EventEditor.kt @@ -27,12 +27,16 @@ import org.matrix.android.sdk.internal.database.mapper.toEntity import org.matrix.android.sdk.internal.session.room.send.LocalEchoEventFactory import org.matrix.android.sdk.internal.session.room.send.LocalEchoRepository import org.matrix.android.sdk.internal.session.room.send.queue.EventSenderProcessor +import org.matrix.android.sdk.internal.util.time.Clock import timber.log.Timber import javax.inject.Inject -internal class EventEditor @Inject constructor(private val eventSenderProcessor: EventSenderProcessor, - private val eventFactory: LocalEchoEventFactory, - private val localEchoRepository: LocalEchoRepository) { +internal class EventEditor @Inject constructor( + private val eventSenderProcessor: EventSenderProcessor, + private val eventFactory: LocalEchoEventFactory, + private val localEchoRepository: LocalEchoRepository, + private val clock: Clock, +) { fun editTextMessage(targetEvent: TimelineEvent, msgType: String, @@ -126,7 +130,7 @@ internal class EventEditor @Inject constructor(private val eventSenderProcessor: } private fun updateFailedEchoWithEvent(roomId: String, failedEchoEventId: String, editedEvent: Event) { - val editedEventEntity = editedEvent.toEntity(roomId, SendState.UNSENT, System.currentTimeMillis()) + val editedEventEntity = editedEvent.toEntity(roomId, SendState.UNSENT, clock.epochMillis()) localEchoRepository.updateEchoAsync(failedEchoEventId) { _, entity -> entity.content = editedEventEntity.content entity.ageLocalTs = editedEventEntity.ageLocalTs diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/relation/threads/FetchThreadSummariesTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/relation/threads/FetchThreadSummariesTask.kt index b596f2288e..c5f9bd13fd 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/relation/threads/FetchThreadSummariesTask.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/relation/threads/FetchThreadSummariesTask.kt @@ -33,6 +33,7 @@ import org.matrix.android.sdk.internal.session.room.timeline.PaginationDirection import org.matrix.android.sdk.internal.session.room.timeline.PaginationResponse import org.matrix.android.sdk.internal.task.Task import org.matrix.android.sdk.internal.util.awaitTransaction +import org.matrix.android.sdk.internal.util.time.Clock import timber.log.Timber import javax.inject.Inject @@ -55,12 +56,14 @@ internal class DefaultFetchThreadSummariesTask @Inject constructor( @SessionDatabase private val monarchy: Monarchy, private val cryptoService: DefaultCryptoService, @UserId private val userId: String, + private val clock: Clock, ) : FetchThreadSummariesTask { override suspend fun execute(params: FetchThreadSummariesTask.Params): Result { val filter = FilterFactory.createThreadsFilter( numberOfEvents = params.limit, - userId = if (params.isUserParticipating) userId else null).toJSONString() + userId = if (params.isUserParticipating) userId else null + ).toJSONString() val response = executeRequest( globalErrorReceiver, @@ -94,7 +97,9 @@ internal class DefaultFetchThreadSummariesTask @Inject constructor( roomMemberContentsByUser = roomMemberContentsByUser, roomEntity = roomEntity, userId = userId, - cryptoService = cryptoService) + cryptoService = cryptoService, + currentTimeMillis = clock.epochMillis(), + ) } } return Result.SUCCESS diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/relation/threads/FetchThreadTimelineTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/relation/threads/FetchThreadTimelineTask.kt index 116d4aa0a1..dabdd04f60 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/relation/threads/FetchThreadTimelineTask.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/relation/threads/FetchThreadTimelineTask.kt @@ -23,7 +23,6 @@ import org.matrix.android.sdk.api.session.events.model.Event import org.matrix.android.sdk.api.session.events.model.EventType import org.matrix.android.sdk.api.session.room.model.RoomMemberContent import org.matrix.android.sdk.api.session.room.send.SendState -import org.matrix.android.sdk.internal.crypto.CryptoSessionInfoProvider import org.matrix.android.sdk.internal.crypto.DefaultCryptoService import org.matrix.android.sdk.internal.database.helper.addTimelineEvent import org.matrix.android.sdk.internal.database.mapper.asDomain @@ -42,7 +41,6 @@ import org.matrix.android.sdk.internal.database.query.getOrCreate import org.matrix.android.sdk.internal.database.query.getOrNull import org.matrix.android.sdk.internal.database.query.where import org.matrix.android.sdk.internal.di.SessionDatabase -import org.matrix.android.sdk.internal.di.UserId import org.matrix.android.sdk.internal.network.GlobalErrorReceiver import org.matrix.android.sdk.internal.network.executeRequest import org.matrix.android.sdk.internal.session.events.getFixedRoomMemberContent @@ -51,6 +49,7 @@ import org.matrix.android.sdk.internal.session.room.relation.RelationsResponse import org.matrix.android.sdk.internal.session.room.timeline.PaginationDirection import org.matrix.android.sdk.internal.task.Task import org.matrix.android.sdk.internal.util.awaitTransaction +import org.matrix.android.sdk.internal.util.time.Clock import timber.log.Timber import javax.inject.Inject @@ -85,10 +84,9 @@ internal interface FetchThreadTimelineTask : Task.addSenderState(realm: Realm, roomId: String, senderId: String) { getOrPut(senderId) { @@ -204,15 +204,15 @@ internal class DefaultFetchThreadTimelineTask @Inject constructor( } /** - * Create an EventEntity to be added in the TimelineEventEntity + * Create an EventEntity to be added in the TimelineEventEntity. */ private fun createEventEntity(roomId: String, event: Event, realm: Realm): EventEntity { - val ageLocalTs = event.unsignedData?.age?.let { System.currentTimeMillis() - it } + val ageLocalTs = event.unsignedData?.age?.let { clock.epochMillis() - it } return event.toEntity(roomId, SendState.SYNCED, ageLocalTs).copyToRealmOrIgnore(realm, EventInsertType.PAGINATION) } /** - * Invoke the event decryption mechanism for a specific event + * Invoke the event decryption mechanism for a specific event. */ private suspend fun decryptIfNeeded(event: Event, roomId: String) { try { diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/DefaultSendService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/DefaultSendService.kt index 34e38581d1..8529365858 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/DefaultSendService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/DefaultSendService.kt @@ -275,7 +275,8 @@ internal class DefaultSendService @AssistedInject constructor( attachment = it, compressBeforeSending = compressBeforeSending, roomIds = roomIds, - rootThreadEventId = rootThreadEventId) + rootThreadEventId = rootThreadEventId + ) } } @@ -297,7 +298,8 @@ internal class DefaultSendService @AssistedInject constructor( localEchoEventFactory.createMediaEvent( roomId = it, attachment = attachment, - rootThreadEventId = rootThreadId).also { event -> + rootThreadEventId = rootThreadId + ).also { event -> createLocalEcho(event) } } @@ -305,7 +307,7 @@ internal class DefaultSendService @AssistedInject constructor( } /** - * We use the roomId of the local echo event + * We use the roomId of the local echo event. */ private fun internalSendMedia(allLocalEchoes: List, attachment: ContentAttachmentData, compressBeforeSending: Boolean): Cancelable { val cancelableBag = CancelableBag() diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/LocalEchoEventFactory.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/LocalEchoEventFactory.kt index bb16563f96..d39088bd6d 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/LocalEchoEventFactory.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/LocalEchoEventFactory.kt @@ -37,13 +37,13 @@ import org.matrix.android.sdk.api.session.room.model.message.LocationAsset import org.matrix.android.sdk.api.session.room.model.message.LocationAssetType import org.matrix.android.sdk.api.session.room.model.message.LocationInfo import org.matrix.android.sdk.api.session.room.model.message.MessageAudioContent +import org.matrix.android.sdk.api.session.room.model.message.MessageBeaconLocationDataContent import org.matrix.android.sdk.api.session.room.model.message.MessageContent import org.matrix.android.sdk.api.session.room.model.message.MessageContentWithFormattedBody import org.matrix.android.sdk.api.session.room.model.message.MessageEndPollContent import org.matrix.android.sdk.api.session.room.model.message.MessageFileContent import org.matrix.android.sdk.api.session.room.model.message.MessageFormat import org.matrix.android.sdk.api.session.room.model.message.MessageImageContent -import org.matrix.android.sdk.api.session.room.model.message.MessageLiveLocationContent import org.matrix.android.sdk.api.session.room.model.message.MessageLocationContent import org.matrix.android.sdk.api.session.room.model.message.MessagePollContent import org.matrix.android.sdk.api.session.room.model.message.MessagePollResponseContent @@ -70,8 +70,8 @@ import org.matrix.android.sdk.internal.di.UserId import org.matrix.android.sdk.internal.session.content.ThumbnailExtractor import org.matrix.android.sdk.internal.session.permalinks.PermalinkFactory import org.matrix.android.sdk.internal.session.room.send.pills.TextPillsUtils +import org.matrix.android.sdk.internal.util.time.Clock import java.util.UUID -import java.util.concurrent.TimeUnit import javax.inject.Inject /** @@ -91,7 +91,8 @@ internal class LocalEchoEventFactory @Inject constructor( private val thumbnailExtractor: ThumbnailExtractor, private val waveformSanitizer: WaveFormSanitizer, private val localEchoRepository: LocalEchoRepository, - private val permalinkFactory: PermalinkFactory + private val permalinkFactory: PermalinkFactory, + private val clock: Clock, ) { fun createTextEvent(roomId: String, msgType: String, text: CharSequence, autoMarkdown: Boolean): Event { if (msgType == MessageType.MSGTYPE_TEXT || msgType == MessageType.MSGTYPE_EMOTE) { @@ -124,7 +125,8 @@ internal class LocalEchoEventFactory @Inject constructor( newBodyAutoMarkdown: Boolean, msgType: String, compatibilityText: String): Event { - return createMessageEvent(roomId, + return createMessageEvent( + roomId, MessageTextContent( msgType = msgType, body = compatibilityText, @@ -132,7 +134,8 @@ internal class LocalEchoEventFactory @Inject constructor( newContent = createTextContent(newBodyText, newBodyAutoMarkdown) .toMessageTextContent(msgType) .toContent() - )) + ) + ) } private fun createPollContent(question: String, @@ -188,7 +191,8 @@ internal class LocalEchoEventFactory @Inject constructor( eventId = localId, type = EventType.POLL_RESPONSE.first(), content = content.toContent(), - unsignedData = UnsignedData(age = null, transactionId = localId)) + unsignedData = UnsignedData(age = null, transactionId = localId) + ) } fun createPollEvent(roomId: String, @@ -204,7 +208,8 @@ internal class LocalEchoEventFactory @Inject constructor( eventId = localId, type = EventType.POLL_START.first(), content = content.toContent(), - unsignedData = UnsignedData(age = null, transactionId = localId)) + unsignedData = UnsignedData(age = null, transactionId = localId) + ) } fun createEndPollEvent(roomId: String, @@ -223,7 +228,8 @@ internal class LocalEchoEventFactory @Inject constructor( eventId = localId, type = EventType.POLL_END.first(), content = content.toContent(), - unsignedData = UnsignedData(age = null, transactionId = localId)) + unsignedData = UnsignedData(age = null, transactionId = localId) + ) } fun createLocationEvent(roomId: String, @@ -238,7 +244,7 @@ internal class LocalEchoEventFactory @Inject constructor( body = geoUri, unstableLocationInfo = LocationInfo(geoUri = geoUri, description = geoUri), unstableLocationAsset = LocationAsset(type = assetType), - unstableTs = TimeUnit.MILLISECONDS.toSeconds(System.currentTimeMillis()), + unstableTimestampMillis = clock.epochMillis(), unstableText = geoUri ) return createMessageEvent(roomId, content) @@ -250,14 +256,14 @@ internal class LocalEchoEventFactory @Inject constructor( longitude: Double, uncertainty: Double?): Event { val geoUri = buildGeoUri(latitude, longitude, uncertainty) - val content = MessageLiveLocationContent( + val content = MessageBeaconLocationDataContent( body = geoUri, relatesTo = RelationDefaultContent( type = RelationType.REFERENCE, eventId = beaconInfoEventId ), unstableLocationInfo = LocationInfo(geoUri = geoUri, description = geoUri), - unstableTimestampAsMilliseconds = TimeUnit.MILLISECONDS.toSeconds(System.currentTimeMillis()), + unstableTimestampMillis = clock.epochMillis(), ) val localId = LocalEcho.createLocalEchoId() return Event( @@ -267,7 +273,8 @@ internal class LocalEchoEventFactory @Inject constructor( eventId = localId, type = EventType.BEACON_LOCATION_DATA.first(), content = content.toContent(), - unsignedData = UnsignedData(age = null, transactionId = localId)) + unsignedData = UnsignedData(age = null, transactionId = localId) + ) } fun createReplaceTextOfReply(roomId: String, @@ -297,7 +304,8 @@ internal class LocalEchoEventFactory @Inject constructor( // val replyFallback = buildReplyFallback(body, originalEvent.root.senderId ?: "", newBodyText) - return createMessageEvent(roomId, + return createMessageEvent( + roomId, MessageTextContent( msgType = msgType, body = compatibilityText, @@ -309,7 +317,8 @@ internal class LocalEchoEventFactory @Inject constructor( formattedBody = replyFormatted ) .toContent() - )) + ) + ) } fun createMediaEvent(roomId: String, @@ -341,7 +350,8 @@ internal class LocalEchoEventFactory @Inject constructor( eventId = localId, type = EventType.REACTION, content = content.toContent(), - unsignedData = UnsignedData(age = null, transactionId = localId)) + unsignedData = UnsignedData(age = null, transactionId = localId) + ) } private fun createImageEvent(roomId: String, attachment: ContentAttachmentData, rootThreadEventId: String?): Event { @@ -496,7 +506,7 @@ internal class LocalEchoEventFactory @Inject constructor( } /** - * Enhance sticker to support threads fallback if needed + * Enhance sticker to support threads fallback if needed. */ private fun enhanceStickerIfNeeded(type: String, content: Content?): Content? { var newContent: Content? = null @@ -516,7 +526,7 @@ internal class LocalEchoEventFactory @Inject constructor( } /** - * Creates a thread event related to the already existing root event + * Creates a thread event related to the already existing root event. */ fun createThreadTextEvent( rootThreadEventId: String, @@ -532,16 +542,18 @@ internal class LocalEchoEventFactory @Inject constructor( content.toThreadTextContent( rootThreadEventId = rootThreadEventId, latestThreadEventId = localEchoRepository.getLatestThreadEvent(rootThreadEventId), - msgType = msgType) - .toContent()) + msgType = msgType + ) + .toContent() + ) } private fun dummyOriginServerTs(): Long { - return System.currentTimeMillis() + return clock.epochMillis() } /** - * Creates a reply to a regular timeline Event or a thread Event if needed + * Creates a reply to a regular timeline Event or a thread Event if needed. */ fun createReplyTextEvent(roomId: String, eventReplied: TimelineEvent, @@ -582,7 +594,9 @@ internal class LocalEchoEventFactory @Inject constructor( relatesTo = generateReplyRelationContent( eventId = eventId, rootThreadEventId = rootThreadEventId, - showInThread = showInThread)) + showInThread = showInThread + ) + ) return createMessageEvent(roomId, content) } @@ -605,7 +619,8 @@ internal class LocalEchoEventFactory @Inject constructor( eventId = it, isFallingBack = showInThread, // False when is a rich reply from within a thread, and true when is a reply that should be visible from threads - inReplyTo = ReplyToContent(eventId = eventId)) + inReplyTo = ReplyToContent(eventId = eventId) + ) } ?: RelationDefaultContent(null, null, ReplyToContent(eventId = eventId)) private fun buildFormattedReply(permalink: String, userLink: String, userId: String, bodyFormatted: String, newBodyFormatted: String): String { @@ -740,13 +755,15 @@ internal class LocalEchoEventFactory @Inject constructor( .toThreadTextContent( rootThreadEventId = rootThreadEventId, latestThreadEventId = localEchoRepository.getLatestThreadEvent(rootThreadEventId), - msgType = MessageType.MSGTYPE_TEXT) + msgType = MessageType.MSGTYPE_TEXT + ) ) } else { createFormattedTextEvent( roomId, markdownParser.parse(quoteText, force = true, advanced = autoMarkdown), - MessageType.MSGTYPE_TEXT) + MessageType.MSGTYPE_TEXT + ) } } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/LocalEchoRepository.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/LocalEchoRepository.kt index 1b1a66a1c4..bed590fd09 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/LocalEchoRepository.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/LocalEchoRepository.kt @@ -43,16 +43,20 @@ import org.matrix.android.sdk.internal.session.room.summary.RoomSummaryUpdater import org.matrix.android.sdk.internal.session.room.timeline.TimelineInput import org.matrix.android.sdk.internal.task.TaskExecutor import org.matrix.android.sdk.internal.util.awaitTransaction +import org.matrix.android.sdk.internal.util.time.Clock import timber.log.Timber import java.util.UUID import javax.inject.Inject -internal class LocalEchoRepository @Inject constructor(@SessionDatabase private val monarchy: Monarchy, - private val taskExecutor: TaskExecutor, - private val realmSessionProvider: RealmSessionProvider, - private val roomSummaryUpdater: RoomSummaryUpdater, - private val timelineInput: TimelineInput, - private val timelineEventMapper: TimelineEventMapper) { +internal class LocalEchoRepository @Inject constructor( + @SessionDatabase private val monarchy: Monarchy, + private val taskExecutor: TaskExecutor, + private val realmSessionProvider: RealmSessionProvider, + private val roomSummaryUpdater: RoomSummaryUpdater, + private val timelineInput: TimelineInput, + private val timelineEventMapper: TimelineEventMapper, + private val clock: Clock, +) { fun createLocalEcho(event: Event) { val roomId = event.roomId ?: throw IllegalStateException("You should have set a roomId for your event") @@ -61,7 +65,7 @@ internal class LocalEchoRepository @Inject constructor(@SessionDatabase private event.type ?: throw IllegalStateException("You should have set a type for your event") val timelineEventEntity = realmSessionProvider.withRealm { realm -> - val eventEntity = event.toEntity(roomId, SendState.UNSENT, System.currentTimeMillis()) + val eventEntity = event.toEntity(roomId, SendState.UNSENT, clock.epochMillis()) val roomMemberHelper = RoomMemberHelper(realm, roomId) val myUser = roomMemberHelper.getLastRoomMember(senderId) val localId = UUID.randomUUID().mostSignificantBits @@ -88,7 +92,7 @@ internal class LocalEchoRepository @Inject constructor(@SessionDatabase private } fun updateSendState(eventId: String, roomId: String?, sendState: SendState, sendStateDetails: String? = null) { - Timber.v("## SendEvent: [${System.currentTimeMillis()}] Update local state of $eventId to ${sendState.name}") + Timber.v("## SendEvent: [${clock.epochMillis()}] Update local state of $eventId to ${sendState.name}") timelineInput.onLocalEchoUpdated(roomId = roomId ?: "", eventId = eventId, sendState = sendState) updateEchoAsync(eventId) { realm, sendingEventEntity -> if (sendState == SendState.SENT && sendingEventEntity.sendState == SendState.SYNCED) { @@ -217,7 +221,7 @@ internal class LocalEchoRepository @Inject constructor(@SessionDatabase private } /** - * Returns the latest known thread event message, or the rootThreadEventId if no other event found + * Returns the latest known thread event message, or the rootThreadEventId if no other event found. */ fun getLatestThreadEvent(rootThreadEventId: String): String { return realmSessionProvider.withRealm { realm -> diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/MarkdownParser.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/MarkdownParser.kt index 05585a4cb5..6a9f86893f 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/MarkdownParser.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/MarkdownParser.kt @@ -78,7 +78,7 @@ internal class MarkdownParser @Inject constructor( text != htmlText && htmlText != "

${text.trim()}

\n" /** - * The parser makes some mistakes, so deal with it here + * The parser makes some mistakes, so deal with it here. */ private fun String.postTreatment(): String { return this diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/MultipleEventSendingDispatcherWorker.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/MultipleEventSendingDispatcherWorker.kt index b59f1b174f..2afca6e554 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/MultipleEventSendingDispatcherWorker.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/MultipleEventSendingDispatcherWorker.kt @@ -33,13 +33,13 @@ import timber.log.Timber import javax.inject.Inject /** - * This worker creates a new work for each events passed in parameter + * This worker creates a new work for each events passed in parameter. * - * Possible previous worker: Always [UploadContentWorker] - * Possible next worker : None, but it will post new work to send events, encrypted or not + * Possible previous worker: Always [UploadContentWorker]. + * Possible next worker : None, but it will post new work to send events, encrypted or not. */ internal class MultipleEventSendingDispatcherWorker(context: Context, params: WorkerParameters, sessionManager: SessionManager) : - SessionSafeCoroutineWorker(context, params, sessionManager, Params::class.java) { + SessionSafeCoroutineWorker(context, params, sessionManager, Params::class.java) { @JsonClass(generateAdapter = true) internal data class Params( @@ -76,10 +76,10 @@ internal class MultipleEventSendingDispatcherWorker(context: Context, params: Wo params.localEchoIds.forEach { localEchoIds -> val roomId = localEchoIds.roomId val eventId = localEchoIds.eventId - localEchoRepository.updateSendState(eventId, roomId, SendState.SENDING) - Timber.v("## SendEvent: [${System.currentTimeMillis()}] Schedule send event $eventId") - val sendWork = createSendEventWork(params.sessionId, eventId, true) - timelineSendEventWorkCommon.postWork(roomId, sendWork) + localEchoRepository.updateSendState(eventId, roomId, SendState.SENDING) + Timber.v("## SendEvent: Schedule send event $eventId") + val sendWork = createSendEventWork(params.sessionId, eventId, true) + timelineSendEventWorkCommon.postWork(roomId, sendWork) } return Result.success() diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/NoMerger.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/NoMerger.kt index b56b283171..6dbd8682d7 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/NoMerger.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/NoMerger.kt @@ -19,7 +19,7 @@ import androidx.work.Data import androidx.work.InputMerger /** - * InputMerger which takes only the first input, to ensure an appended work will only have the specified parameters + * InputMerger which takes only the first input, to ensure an appended work will only have the specified parameters. */ internal class NoMerger : InputMerger() { override fun merge(inputs: MutableList): Data { diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/RedactEventWorker.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/RedactEventWorker.kt index c03d1fa81e..1c0da4839a 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/RedactEventWorker.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/RedactEventWorker.kt @@ -30,11 +30,11 @@ import org.matrix.android.sdk.internal.worker.WorkerParamsFactory import javax.inject.Inject /** - * Possible previous worker: None - * Possible next worker : None + * Possible previous worker: None. + * Possible next worker : None. */ internal class RedactEventWorker(context: Context, params: WorkerParameters, sessionManager: SessionManager) : - SessionSafeCoroutineWorker(context, params, sessionManager, Params::class.java) { + SessionSafeCoroutineWorker(context, params, sessionManager, Params::class.java) { @JsonClass(generateAdapter = true) internal data class Params( @@ -74,9 +74,13 @@ internal class RedactEventWorker(context: Context, params: WorkerParameters, ses else -> { // TODO mark as failed to send? // always return success, or the chain will be stuck for ever! - Result.success(WorkerParamsFactory.toData(params.copy( - lastFailureMessage = it.localizedMessage - ))) + Result.success( + WorkerParamsFactory.toData( + params.copy( + lastFailureMessage = it.localizedMessage + ) + ) + ) } } } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/SendEventWorker.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/SendEventWorker.kt index 7f24688ece..bea6069dd6 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/SendEventWorker.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/SendEventWorker.kt @@ -36,11 +36,11 @@ import javax.inject.Inject // private const val MAX_NUMBER_OF_RETRY_BEFORE_FAILING = 3 /** - * Possible previous worker: [EncryptEventWorker] or first worker - * Possible next worker : None + * Possible previous worker: [EncryptEventWorker] or first worker. + * Possible next worker : None. */ internal class SendEventWorker(context: Context, params: WorkerParameters, sessionManager: SessionManager) : - SessionSafeCoroutineWorker(context, params, sessionManager, Params::class.java) { + SessionSafeCoroutineWorker(context, params, sessionManager, Params::class.java) { @JsonClass(generateAdapter = true) internal data class Params( @@ -89,13 +89,13 @@ internal class SendEventWorker(context: Context, params: WorkerParameters, sessi .also { Timber.e("Work cancelled due to input error from parent") } } - Timber.v("## SendEvent: [${System.currentTimeMillis()}] Send event ${params.eventId}") + Timber.v("## SendEvent: Send event ${params.eventId}") return try { sendEventTask.execute(SendEventTask.Params(event, params.isEncrypted ?: cryptoService.isRoomEncrypted(event.roomId))) Result.success() } catch (exception: Throwable) { if (/*currentAttemptCount >= MAX_NUMBER_OF_RETRY_BEFORE_FAILING ||**/ !exception.shouldBeRetried()) { - Timber.e("## SendEvent: [${System.currentTimeMillis()}] Send event Failed cannot retry ${params.eventId} > ${exception.localizedMessage}") + Timber.e("## SendEvent: Send event Failed cannot retry ${params.eventId} > ${exception.localizedMessage}") localEchoRepository.updateSendState( eventId = event.eventId, roomId = event.roomId, @@ -104,7 +104,7 @@ internal class SendEventWorker(context: Context, params: WorkerParameters, sessi ) Result.success() } else { - Timber.e("## SendEvent: [${System.currentTimeMillis()}] Send event Failed schedule retry ${params.eventId} > ${exception.localizedMessage}") + Timber.e("## SendEvent: Send event Failed schedule retry ${params.eventId} > ${exception.localizedMessage}") Result.retry() } } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/TextContentExtension.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/TextContentExtension.kt index 8caa99d90a..49bc05f40c 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/TextContentExtension.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/TextContentExtension.kt @@ -54,7 +54,8 @@ internal fun TextContent.toThreadTextContent( isFallingBack = true, inReplyTo = ReplyToContent( eventId = latestThreadEventId - )), + ) + ), formattedBody = formattedText ) } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/queue/EventSenderProcessorCoroutine.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/queue/EventSenderProcessorCoroutine.kt index 5b4efa5df6..8ef631ad36 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/queue/EventSenderProcessorCoroutine.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/queue/EventSenderProcessorCoroutine.kt @@ -64,12 +64,12 @@ internal class EventSenderProcessorCoroutine @Inject constructor( private val waitForNetworkSequencer = SemaphoreCoroutineSequencer() /** - * sequencers use QueuedTask.queueIdentifier as key + * sequencers use QueuedTask.queueIdentifier as key. */ private val sequencers = ConcurrentHashMap() /** - * cancelableBag use QueuedTask.taskIdentifier as key + * cancelableBag use QueuedTask.taskIdentifier as key. */ private val cancelableBag = ConcurrentHashMap() @@ -191,7 +191,7 @@ internal class EventSenderProcessorCoroutine @Inject constructor( private suspend fun QueuedTask.waitForNetwork() = waitForNetworkSequencer.post { while (!canReachServer.get()) { - Timber.v("## $this cannot reach server wait ts:${System.currentTimeMillis()}") + Timber.v("## $this cannot reach server wait for $$RETRY_WAIT_TIME_MS ms") delay(RETRY_WAIT_TIME_MS) withContext(Dispatchers.IO) { val hostAvailable = HomeServerAvailabilityChecker(sessionParams).check() diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/queue/EventSenderProcessorThread.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/queue/EventSenderProcessorThread.kt index 1ee3139194..301f8cb9d6 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/queue/EventSenderProcessorThread.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/queue/EventSenderProcessorThread.kt @@ -136,7 +136,7 @@ internal class EventSenderProcessorThread @Inject constructor( private var retryNoNetworkTask: TimerTask? = null override fun run() { - Timber.v("## SendThread started ts:${System.currentTimeMillis()}") + Timber.v("## SendThread started") try { while (!isInterrupted) { Timber.v("## SendThread wait for task to process") @@ -151,7 +151,7 @@ internal class EventSenderProcessorThread @Inject constructor( } // we check for network connectivity while (!canReachServer) { - Timber.v("## SendThread cannot reach server, wait ts:${System.currentTimeMillis()}") + Timber.v("## SendThread cannot reach server") // schedule to retry waitForNetwork() // if thread as been killed meanwhile @@ -175,7 +175,7 @@ internal class EventSenderProcessorThread @Inject constructor( canReachServer = false if (task.retryCount.getAndIncrement() >= 3) task.onTaskFailed() while (!canReachServer) { - Timber.v("## SendThread retryLoop cannot reach server, wait ts:${System.currentTimeMillis()}") + Timber.v("## SendThread retryLoop cannot reach server") // schedule to retry waitForNetwork() } @@ -218,7 +218,7 @@ internal class EventSenderProcessorThread @Inject constructor( // state = State.KILLED // is this needed? retryNoNetworkTask?.cancel() - Timber.w("## SendThread finished ${System.currentTimeMillis()}") + Timber.w("## SendThread finished") } private fun waitForNetwork() { diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/queue/QueueMemento.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/queue/QueueMemento.kt index 116c8d5c6b..545fc41737 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/queue/QueueMemento.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/queue/QueueMemento.kt @@ -74,7 +74,7 @@ internal class QueueMemento @Inject constructor(context: Context, encrypt = task.encrypt, order = order ) - is RedactQueuedTask -> RedactEventTaskInfo( + is RedactQueuedTask -> RedactEventTaskInfo( redactionLocalEcho = task.redactionLocalEchoId, order = order ) @@ -92,7 +92,7 @@ internal class QueueMemento @Inject constructor(context: Context, ?.forEach { info -> try { when (info) { - is SendEventTaskInfo -> { + is SendEventTaskInfo -> { localEchoRepository.getUpToDateEcho(info.localEchoId)?.let { if (it.sendState.isSending() && it.eventId != null && it.roomId != null) { localEchoRepository.updateSendState(it.eventId, it.roomId, SendState.UNSENT) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/queue/TaskInfo.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/queue/TaskInfo.kt index a03ab778f6..e216e5109f 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/queue/TaskInfo.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/queue/TaskInfo.kt @@ -24,8 +24,8 @@ import org.matrix.android.sdk.internal.di.SerializeNulls import org.matrix.android.sdk.internal.network.parsing.RuntimeJsonAdapterFactory /** - * Info that need to be persisted by the sender thread - * With polymorphic moshi parsing + * Info that need to be persisted by the sender thread. + * With polymorphic moshi parsing. */ internal interface TaskInfo { val type: String @@ -37,9 +37,10 @@ internal interface TaskInfo { const val TYPE_REDACT = "TYPE_REDACT" private val moshi = Moshi.Builder() - .add(RuntimeJsonAdapterFactory.of(TaskInfo::class.java, "type", FallbackTaskInfo::class.java) - .registerSubtype(SendEventTaskInfo::class.java, TYPE_SEND) - .registerSubtype(RedactEventTaskInfo::class.java, TYPE_REDACT) + .add( + RuntimeJsonAdapterFactory.of(TaskInfo::class.java, "type", FallbackTaskInfo::class.java) + .registerSubtype(SendEventTaskInfo::class.java, TYPE_SEND) + .registerSubtype(RedactEventTaskInfo::class.java, TYPE_REDACT) ) .add(SerializeNulls.JSON_ADAPTER_FACTORY) .build() diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/state/DefaultStateService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/state/DefaultStateService.kt index 89d33f98d2..60231892e1 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/state/DefaultStateService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/state/DefaultStateService.kt @@ -33,8 +33,7 @@ import org.matrix.android.sdk.api.session.room.model.RoomHistoryVisibility import org.matrix.android.sdk.api.session.room.model.RoomJoinRules import org.matrix.android.sdk.api.session.room.model.RoomJoinRulesAllowEntry import org.matrix.android.sdk.api.session.room.model.RoomJoinRulesContent -import org.matrix.android.sdk.api.session.room.model.livelocation.BeaconInfo -import org.matrix.android.sdk.api.session.room.model.livelocation.LiveLocationBeaconContent +import org.matrix.android.sdk.api.session.room.model.message.MessageBeaconInfoContent import org.matrix.android.sdk.api.session.room.state.StateService import org.matrix.android.sdk.api.util.JsonDict import org.matrix.android.sdk.api.util.MimeTypes @@ -74,14 +73,14 @@ internal class DefaultStateService @AssistedInject constructor(@Assisted private eventType: String, stateKey: String, body: JsonDict - ) { + ): String { val params = SendStateTask.Params( roomId = roomId, stateKey = stateKey, eventType = eventType, body = body.toSafeJson(eventType) ) - sendStateTask.executeRetry(params, 3) + return sendStateTask.executeRetry(params, 3) } private fun JsonDict.toSafeJson(eventType: String): JsonDict { @@ -137,7 +136,7 @@ internal class DefaultStateService @AssistedInject constructor(@Assisted private if (joinRules != null) { val body = if (joinRules == RoomJoinRules.RESTRICTED) { RoomJoinRulesContent( - _joinRules = RoomJoinRules.RESTRICTED.value, + joinRulesStr = RoomJoinRules.RESTRICTED.value, allowList = allowList ).toContent() } else { @@ -193,20 +192,13 @@ internal class DefaultStateService @AssistedInject constructor(@Assisted private override suspend fun stopLiveLocation(userId: String) { getLiveLocationBeaconInfo(userId, true)?.let { beaconInfoStateEvent -> - beaconInfoStateEvent.getClearContent()?.toModel()?.let { content -> - val beaconContent = LiveLocationBeaconContent( - unstableBeaconInfo = BeaconInfo( - description = content.getBestBeaconInfo()?.description, - timeout = content.getBestBeaconInfo()?.timeout, - isLive = false, - ), - unstableTimestampAsMilliseconds = System.currentTimeMillis() - ).toContent() + beaconInfoStateEvent.getClearContent()?.toModel()?.let { content -> + val updatedContent = content.copy(isLive = false).toContent() beaconInfoStateEvent.stateKey?.let { sendStateEvent( eventType = EventType.STATE_ROOM_BEACON_INFO.first(), - body = beaconContent, + body = updatedContent, stateKey = it ) } @@ -225,7 +217,7 @@ internal class DefaultStateService @AssistedInject constructor(@Assisted private } .firstOrNull { beaconInfoEvent -> !filterOnlyLive || - beaconInfoEvent.getClearContent()?.toModel()?.getBestBeaconInfo()?.isLive.orFalse() + beaconInfoEvent.getClearContent()?.toModel()?.isLive.orFalse() } } } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/state/SafePowerLevelContent.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/state/SafePowerLevelContent.kt index 197b4f8688..b0c795950e 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/state/SafePowerLevelContent.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/state/SafePowerLevelContent.kt @@ -23,8 +23,11 @@ import org.matrix.android.sdk.api.session.events.model.toModel import org.matrix.android.sdk.api.session.room.model.PowerLevelsContent import org.matrix.android.sdk.api.util.JsonDict +/** + * Serializable object. + */ @JsonClass(generateAdapter = true) -internal data class SerializablePowerLevelsContent( +internal data class SafePowerLevelContent( @Json(name = "ban") val ban: Int?, @Json(name = "kick") val kick: Int?, @Json(name = "invite") val invite: Int?, @@ -41,7 +44,7 @@ internal data class SerializablePowerLevelsContent( internal fun JsonDict.toSafePowerLevelsContentDict(): JsonDict { return toModel() ?.let { content -> - SerializablePowerLevelsContent( + SafePowerLevelContent( ban = content.ban, kick = content.kick, invite = content.invite, @@ -51,7 +54,7 @@ internal fun JsonDict.toSafePowerLevelsContentDict(): JsonDict { usersDefault = content.usersDefault, users = content.users, stateDefault = content.stateDefault, - notifications = content.notifications?.mapValues { content.notificationLevel(it.key) } + notifications = content.notifications?.mapValues { content.notificationLevel(it.key) } ) } ?.toContent() diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/state/SendStateTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/state/SendStateTask.kt index 56c69a05a6..59c9de2932 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/state/SendStateTask.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/state/SendStateTask.kt @@ -21,9 +21,10 @@ import org.matrix.android.sdk.internal.network.GlobalErrorReceiver import org.matrix.android.sdk.internal.network.executeRequest import org.matrix.android.sdk.internal.session.room.RoomAPI import org.matrix.android.sdk.internal.task.Task +import timber.log.Timber import javax.inject.Inject -internal interface SendStateTask : Task { +internal interface SendStateTask : Task { data class Params( val roomId: String, val stateKey: String, @@ -37,9 +38,9 @@ internal class DefaultSendStateTask @Inject constructor( private val globalErrorReceiver: GlobalErrorReceiver ) : SendStateTask { - override suspend fun execute(params: SendStateTask.Params) { + override suspend fun execute(params: SendStateTask.Params): String { return executeRequest(globalErrorReceiver) { - if (params.stateKey.isEmpty()) { + val response = if (params.stateKey.isEmpty()) { roomAPI.sendStateEvent( roomId = params.roomId, stateEventType = params.eventType, @@ -53,6 +54,9 @@ internal class DefaultSendStateTask @Inject constructor( params = params.body ) } + response.eventId.also { + Timber.d("State event: $it just sent in room ${params.roomId}") + } } } } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/summary/GraphUtils.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/summary/GraphUtils.kt index e3a215445d..496bc7097f 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/summary/GraphUtils.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/summary/GraphUtils.kt @@ -71,7 +71,7 @@ internal class Graph { } /** - * Depending on the chosen starting point the background edge might change + * Depending on the chosen starting point the background edge might change. */ fun findBackwardEdges(startFrom: GraphNode? = null): List { val backwardEdges = mutableSetOf() @@ -101,11 +101,11 @@ internal class Graph { // it's a candidate destination = it.destination } - inPath -> { + inPath -> { // Cycle!! backwardEdges.add(it) } - completed -> { + completed -> { // dead end } } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/summary/RoomSummaryDataSource.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/summary/RoomSummaryDataSource.kt index 18a4f80547..96e8d3c73f 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/summary/RoomSummaryDataSource.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/summary/RoomSummaryDataSource.kt @@ -26,7 +26,6 @@ import com.zhuinden.monarchy.Monarchy import io.realm.Realm import io.realm.RealmQuery import io.realm.kotlin.where -import org.matrix.android.sdk.api.MatrixCoroutineDispatchers import org.matrix.android.sdk.api.query.ActiveSpaceFilter import org.matrix.android.sdk.api.query.RoomCategoryFilter import org.matrix.android.sdk.api.query.isNormalized @@ -43,7 +42,6 @@ import org.matrix.android.sdk.api.session.room.summary.RoomAggregateNotification import org.matrix.android.sdk.api.session.space.SpaceSummaryQueryParams import org.matrix.android.sdk.api.util.Optional import org.matrix.android.sdk.api.util.toOptional -import org.matrix.android.sdk.internal.database.RealmSessionProvider import org.matrix.android.sdk.internal.database.mapper.RoomSummaryMapper import org.matrix.android.sdk.internal.database.model.RoomSummaryEntity import org.matrix.android.sdk.internal.database.model.RoomSummaryEntityFields @@ -57,10 +55,8 @@ import javax.inject.Inject internal class RoomSummaryDataSource @Inject constructor( @SessionDatabase private val monarchy: Monarchy, - private val realmSessionProvider: RealmSessionProvider, private val roomSummaryMapper: RoomSummaryMapper, private val queryStringValueProcessor: QueryStringValueProcessor, - private val coroutineDispatchers: MatrixCoroutineDispatchers ) { fun getRoomSummary(roomIdOrAlias: String): RoomSummary? { diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/summary/RoomSummaryUpdater.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/summary/RoomSummaryUpdater.kt index 3af579d050..611d6dc65e 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/summary/RoomSummaryUpdater.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/summary/RoomSummaryUpdater.kt @@ -212,7 +212,7 @@ internal class RoomSummaryUpdater @Inject constructor( } /** - * Should be called at the end of the room sync, to check and validate all parent/child relations + * Should be called at the end of the room sync, to check and validate all parent/child relations. */ fun validateSpaceRelationship(realm: Realm) { measureTimeMillis { diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/threads/DefaultThreadsService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/threads/DefaultThreadsService.kt index b65991347d..6c6d6368d1 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/threads/DefaultThreadsService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/threads/DefaultThreadsService.kt @@ -72,17 +72,21 @@ internal class DefaultThreadsService @AssistedInject constructor( } override suspend fun fetchThreadTimeline(rootThreadEventId: String, from: String, limit: Int) { - fetchThreadTimelineTask.execute(FetchThreadTimelineTask.Params( - roomId = roomId, - rootThreadEventId = rootThreadEventId, - from = from, - limit = limit - )) + fetchThreadTimelineTask.execute( + FetchThreadTimelineTask.Params( + roomId = roomId, + rootThreadEventId = rootThreadEventId, + from = from, + limit = limit + ) + ) } override suspend fun fetchThreadSummaries() { - fetchThreadSummariesTask.execute(FetchThreadSummariesTask.Params( - roomId = roomId - )) + fetchThreadSummariesTask.execute( + FetchThreadSummariesTask.Params( + roomId = roomId + ) + ) } } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/threads/local/DefaultThreadsLocalService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/threads/local/DefaultThreadsLocalService.kt index 3bc36fb2a8..296981dfc8 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/threads/local/DefaultThreadsLocalService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/threads/local/DefaultThreadsLocalService.kt @@ -83,7 +83,8 @@ internal class DefaultThreadsLocalService @AssistedInject constructor( realm = it, roomId = roomId, rootThreadEventId = rootThreadEventId, - senderId = userId) + senderId = userId + ) } } @@ -97,7 +98,8 @@ internal class DefaultThreadsLocalService @AssistedInject constructor( monarchy.awaitTransaction { EventEntity.where( realm = it, - eventId = rootThreadEventId).findFirst()?.threadNotificationState = ThreadNotificationState.NO_NEW_MESSAGE + eventId = rootThreadEventId + ).findFirst()?.threadNotificationState = ThreadNotificationState.NO_NEW_MESSAGE } } } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/DefaultTimeline.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/DefaultTimeline.kt index 5064ebf49b..fad21c0918 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/DefaultTimeline.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/DefaultTimeline.kt @@ -39,32 +39,38 @@ import org.matrix.android.sdk.api.settings.LightweightSettingsStorage import org.matrix.android.sdk.internal.database.mapper.TimelineEventMapper import org.matrix.android.sdk.internal.session.room.membership.LoadRoomMembersTask import org.matrix.android.sdk.internal.session.room.relation.threads.FetchThreadTimelineTask +import org.matrix.android.sdk.internal.session.room.state.StateEventDataSource import org.matrix.android.sdk.internal.session.sync.handler.room.ReadReceiptHandler import org.matrix.android.sdk.internal.session.sync.handler.room.ThreadsAwarenessHandler import org.matrix.android.sdk.internal.task.SemaphoreCoroutineSequencer import org.matrix.android.sdk.internal.util.createBackgroundHandler +import org.matrix.android.sdk.internal.util.time.Clock import timber.log.Timber import java.util.UUID import java.util.concurrent.CopyOnWriteArrayList import java.util.concurrent.atomic.AtomicBoolean import java.util.concurrent.atomic.AtomicReference -internal class DefaultTimeline(private val roomId: String, - private val initialEventId: String?, - private val realmConfiguration: RealmConfiguration, - private val loadRoomMembersTask: LoadRoomMembersTask, - private val readReceiptHandler: ReadReceiptHandler, - private val settings: TimelineSettings, - private val coroutineDispatchers: MatrixCoroutineDispatchers, - paginationTask: PaginationTask, - getEventTask: GetContextOfEventTask, - fetchTokenAndPaginateTask: FetchTokenAndPaginateTask, - fetchThreadTimelineTask: FetchThreadTimelineTask, - timelineEventMapper: TimelineEventMapper, - timelineInput: TimelineInput, - threadsAwarenessHandler: ThreadsAwarenessHandler, - lightweightSettingsStorage: LightweightSettingsStorage, - eventDecryptor: TimelineEventDecryptor) : Timeline { +internal class DefaultTimeline( + private val roomId: String, + private val initialEventId: String?, + private val realmConfiguration: RealmConfiguration, + private val loadRoomMembersTask: LoadRoomMembersTask, + private val readReceiptHandler: ReadReceiptHandler, + private val settings: TimelineSettings, + private val coroutineDispatchers: MatrixCoroutineDispatchers, + private val clock: Clock, + stateEventDataSource: StateEventDataSource, + paginationTask: PaginationTask, + getEventTask: GetContextOfEventTask, + fetchTokenAndPaginateTask: FetchTokenAndPaginateTask, + fetchThreadTimelineTask: FetchThreadTimelineTask, + timelineEventMapper: TimelineEventMapper, + timelineInput: TimelineInput, + threadsAwarenessHandler: ThreadsAwarenessHandler, + lightweightSettingsStorage: LightweightSettingsStorage, + eventDecryptor: TimelineEventDecryptor, +) : Timeline { companion object { val BACKGROUND_HANDLER = createBackgroundHandler("DefaultTimeline_Thread") @@ -100,8 +106,11 @@ internal class DefaultTimeline(private val roomId: String, threadsAwarenessHandler = threadsAwarenessHandler, lightweightSettingsStorage = lightweightSettingsStorage, onEventsUpdated = this::sendSignalToPostSnapshot, + onEventsDeleted = this::onEventsDeleted, onLimitedTimeline = this::onLimitedTimeline, - onNewTimelineEvents = this::onNewTimelineEvents + onNewTimelineEvents = this::onNewTimelineEvents, + stateEventDataSource = stateEventDataSource, + matrixCoroutineDispatchers = coroutineDispatchers, ) private var strategy: LoadTimelineStrategy = buildStrategy(LoadTimelineStrategy.Mode.Live) @@ -286,7 +295,6 @@ internal class DefaultTimeline(private val roomId: String, } } - @Suppress("EXPERIMENTAL_API_USAGE") private fun listenToPostSnapshotSignals() { postSnapshotSignalFlow .sample(150) @@ -304,6 +312,12 @@ internal class DefaultTimeline(private val roomId: String, } } + private fun onEventsDeleted() { + // Some event have been deleted, for instance when a user has been ignored. + // Restart the timeline (live) + restartWithEventId(null) + } + private suspend fun postSnapshot() { val snapshot = strategy.buildSnapshot() Timber.v("Post snapshot of ${snapshot.size} events") @@ -363,7 +377,8 @@ internal class DefaultTimeline(private val roomId: String, roomId = roomId, timelineId = timelineID, mode = mode, - dependencies = strategyDependencies + dependencies = strategyDependencies, + clock = clock, ) } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/DefaultTimelineService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/DefaultTimelineService.kt index 826c9d7c48..53c0253876 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/DefaultTimelineService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/DefaultTimelineService.kt @@ -32,8 +32,10 @@ import org.matrix.android.sdk.internal.database.mapper.TimelineEventMapper import org.matrix.android.sdk.internal.di.SessionDatabase import org.matrix.android.sdk.internal.session.room.membership.LoadRoomMembersTask import org.matrix.android.sdk.internal.session.room.relation.threads.FetchThreadTimelineTask +import org.matrix.android.sdk.internal.session.room.state.StateEventDataSource import org.matrix.android.sdk.internal.session.sync.handler.room.ReadReceiptHandler import org.matrix.android.sdk.internal.session.sync.handler.room.ThreadsAwarenessHandler +import org.matrix.android.sdk.internal.util.time.Clock internal class DefaultTimelineService @AssistedInject constructor( @Assisted private val roomId: String, @@ -50,7 +52,9 @@ internal class DefaultTimelineService @AssistedInject constructor( private val lightweightSettingsStorage: LightweightSettingsStorage, private val readReceiptHandler: ReadReceiptHandler, private val coroutineDispatchers: MatrixCoroutineDispatchers, - private val timelineEventDataSource: TimelineEventDataSource + private val timelineEventDataSource: TimelineEventDataSource, + private val clock: Clock, + private val stateEventDataSource: StateEventDataSource, ) : TimelineService { @AssistedFactory @@ -75,7 +79,9 @@ internal class DefaultTimelineService @AssistedInject constructor( readReceiptHandler = readReceiptHandler, getEventTask = contextOfEventTask, threadsAwarenessHandler = threadsAwarenessHandler, - lightweightSettingsStorage = lightweightSettingsStorage + lightweightSettingsStorage = lightweightSettingsStorage, + clock = clock, + stateEventDataSource = stateEventDataSource, ) } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/GetEventTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/GetEventTask.kt index 5bca5118b8..aef9e24c8b 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/GetEventTask.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/GetEventTask.kt @@ -24,6 +24,7 @@ import org.matrix.android.sdk.internal.network.GlobalErrorReceiver import org.matrix.android.sdk.internal.network.executeRequest import org.matrix.android.sdk.internal.session.room.RoomAPI import org.matrix.android.sdk.internal.task.Task +import org.matrix.android.sdk.internal.util.time.Clock import javax.inject.Inject internal interface GetEventTask : Task { @@ -36,7 +37,8 @@ internal interface GetEventTask : Task { internal class DefaultGetEventTask @Inject constructor( private val roomAPI: RoomAPI, private val globalErrorReceiver: GlobalErrorReceiver, - private val eventDecryptor: EventDecryptor + private val eventDecryptor: EventDecryptor, + private val clock: Clock, ) : GetEventTask { override suspend fun execute(params: GetEventTask.Params): Event { @@ -59,7 +61,7 @@ internal class DefaultGetEventTask @Inject constructor( } } - event.ageLocalTs = event.unsignedData?.age?.let { System.currentTimeMillis() - it } + event.ageLocalTs = event.unsignedData?.age?.let { clock.epochMillis() - it } return event } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/LiveRoomStateListener.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/LiveRoomStateListener.kt new file mode 100644 index 0000000000..b2692bf805 --- /dev/null +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/LiveRoomStateListener.kt @@ -0,0 +1,66 @@ +/* + * Copyright (c) 2022 The Matrix.org Foundation C.I.C. + * + * 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 org.matrix.android.sdk.internal.session.room.timeline + +import androidx.lifecycle.LiveData +import androidx.lifecycle.Observer +import kotlinx.coroutines.CoroutineDispatcher +import kotlinx.coroutines.withContext +import org.matrix.android.sdk.api.query.QueryStringValue +import org.matrix.android.sdk.api.session.events.model.Event +import org.matrix.android.sdk.api.session.events.model.EventType +import org.matrix.android.sdk.api.session.room.model.RoomMemberContent +import org.matrix.android.sdk.internal.session.events.getFixedRoomMemberContent +import org.matrix.android.sdk.internal.session.room.state.StateEventDataSource + +/** + * Helper to observe and query the live room state. + */ +internal class LiveRoomStateListener( + roomId: String, + stateEventDataSource: StateEventDataSource, + private val mainDispatcher: CoroutineDispatcher, +) { + private val roomStateObserver = Observer> { stateEvents -> + stateEvents.map { event -> + val memberContent = event.getFixedRoomMemberContent() ?: return@map + val stateKey = event.stateKey ?: return@map + liveRoomState[stateKey] = memberContent + } + } + private val stateEventsLiveData: LiveData> by lazy { + stateEventDataSource.getStateEventsLive( + roomId = roomId, + eventTypes = setOf(EventType.STATE_ROOM_MEMBER), + stateKey = QueryStringValue.NoCondition, + ) + } + + private val liveRoomState = mutableMapOf() + + suspend fun start() = withContext(mainDispatcher) { + stateEventsLiveData.observeForever(roomStateObserver) + } + + suspend fun stop() = withContext(mainDispatcher) { + if (stateEventsLiveData.hasActiveObservers()) { + stateEventsLiveData.removeObserver(roomStateObserver) + } + } + + fun getLiveState(stateKey: String): RoomMemberContent? = liveRoomState[stateKey] +} diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/LiveTimelineEvent.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/LiveTimelineEvent.kt index 64b1a4ff1d..e765e05578 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/LiveTimelineEvent.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/LiveTimelineEvent.kt @@ -41,7 +41,7 @@ internal class LiveTimelineEvent(private val monarchy: Monarchy, private val timelineEventMapper: TimelineEventMapper, private val roomId: String, private val eventId: String) : - MediatorLiveData>() { + MediatorLiveData>() { init { buildAndObserveQuery() diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/LoadTimelineStrategy.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/LoadTimelineStrategy.kt index ff986d04af..4f65f85ce4 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/LoadTimelineStrategy.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/LoadTimelineStrategy.kt @@ -23,6 +23,7 @@ import io.realm.RealmConfiguration import io.realm.RealmResults import io.realm.kotlin.createObject import kotlinx.coroutines.CompletableDeferred +import org.matrix.android.sdk.api.MatrixCoroutineDispatchers import org.matrix.android.sdk.api.extensions.orFalse import org.matrix.android.sdk.api.failure.Failure import org.matrix.android.sdk.api.failure.MatrixError @@ -41,7 +42,9 @@ import org.matrix.android.sdk.internal.database.query.findAllIncludingEvents import org.matrix.android.sdk.internal.database.query.findLastForwardChunkOfThread import org.matrix.android.sdk.internal.database.query.where import org.matrix.android.sdk.internal.session.room.relation.threads.FetchThreadTimelineTask +import org.matrix.android.sdk.internal.session.room.state.StateEventDataSource import org.matrix.android.sdk.internal.session.sync.handler.room.ThreadsAwarenessHandler +import org.matrix.android.sdk.internal.util.time.Clock import timber.log.Timber import java.util.concurrent.atomic.AtomicReference @@ -53,11 +56,13 @@ import java.util.concurrent.atomic.AtomicReference * Once we got a ChunkEntity we wrap it with TimelineChunk class so we dispatch any methods for loading data. */ -internal class LoadTimelineStrategy( +internal class LoadTimelineStrategy constructor( private val roomId: String, private val timelineId: String, private val mode: Mode, - private val dependencies: Dependencies) { + private val dependencies: Dependencies, + clock: Clock, +) { sealed interface Mode { object Live : Mode @@ -95,8 +100,11 @@ internal class LoadTimelineStrategy( val threadsAwarenessHandler: ThreadsAwarenessHandler, val lightweightSettingsStorage: LightweightSettingsStorage, val onEventsUpdated: (Boolean) -> Unit, + val onEventsDeleted: () -> Unit, val onLimitedTimeline: () -> Unit, - val onNewTimelineEvents: (List) -> Unit + val onNewTimelineEvents: (List) -> Unit, + val stateEventDataSource: StateEventDataSource, + val matrixCoroutineDispatchers: MatrixCoroutineDispatchers, ) private var getContextLatch: CompletableDeferred? = null @@ -152,7 +160,7 @@ internal class LoadTimelineStrategy( } } - private val uiEchoManager = UIEchoManager(uiEchoManagerListener) + private val uiEchoManager = UIEchoManager(uiEchoManagerListener, clock) private val sendingEventsDataSource: SendingEventsDataSource = RealmSendingEventsDataSource( roomId = roomId, realm = dependencies.realm, @@ -161,7 +169,13 @@ internal class LoadTimelineStrategy( onEventsUpdated = dependencies.onEventsUpdated ) - fun onStart() { + private val liveRoomStateListener = LiveRoomStateListener( + roomId, + dependencies.stateEventDataSource, + dependencies.matrixCoroutineDispatchers.main + ) + + suspend fun onStart() { dependencies.eventDecryptor.start() dependencies.timelineInput.listeners.add(timelineInputListener) val realm = dependencies.realm.get() @@ -170,9 +184,13 @@ internal class LoadTimelineStrategy( it.addChangeListener(chunkEntityListener) timelineChunk = it.createTimelineChunk() } + + if (dependencies.timelineSettings.useLiveSenderInfo) { + liveRoomStateListener.start() + } } - fun onStop() { + suspend fun onStop() { dependencies.eventDecryptor.destroy() dependencies.timelineInput.listeners.remove(timelineInputListener) chunkEntity?.removeChangeListener(chunkEntityListener) @@ -184,6 +202,9 @@ internal class LoadTimelineStrategy( if (mode is Mode.Thread) { clearThreadChunkEntity(dependencies.realm.get(), mode.rootThreadEventId) } + if (dependencies.timelineSettings.useLiveSenderInfo) { + liveRoomStateListener.stop() + } } suspend fun loadMore(count: Int, direction: Timeline.Direction, fetchOnServerIfNeeded: Boolean = true): LoadMoreResult { @@ -218,7 +239,22 @@ internal class LoadTimelineStrategy( } fun buildSnapshot(): List { - return buildSendingEvents() + timelineChunk?.builtItems(includesNext = true, includesPrev = true).orEmpty() + val events = buildSendingEvents() + timelineChunk?.builtItems(includesNext = true, includesPrev = true).orEmpty() + return if (dependencies.timelineSettings.useLiveSenderInfo) { + events.map(this::applyLiveRoomState) + } else { + events + } + } + + private fun applyLiveRoomState(event: TimelineEvent): TimelineEvent { + val updatedState = liveRoomStateListener.getLiveState(event.senderInfo.userId) + return if (updatedState != null) { + val updatedSenderInfo = event.senderInfo.copy(avatarUrl = updatedState.avatarUrl, displayName = updatedState.displayName) + event.copy(senderInfo = updatedSenderInfo) + } else { + event + } } private fun buildSendingEvents(): List { @@ -251,7 +287,7 @@ internal class LoadTimelineStrategy( /** * Clear any existing thread chunk entity and create a new one, with the - * rootThreadEventId included + * rootThreadEventId included. */ private fun recreateThreadChunkEntity(realm: Realm, rootThreadEventId: String) { realm.executeTransaction { @@ -271,7 +307,7 @@ internal class LoadTimelineStrategy( } /** - * Clear any existing thread chunk + * Clear any existing thread chunk. */ private fun clearThreadChunkEntity(realm: Realm, rootThreadEventId: String) { realm.executeTransaction { @@ -302,7 +338,8 @@ internal class LoadTimelineStrategy( threadsAwarenessHandler = dependencies.threadsAwarenessHandler, lightweightSettingsStorage = dependencies.lightweightSettingsStorage, initialEventId = mode.originEventId(), - onBuiltEvents = dependencies.onEventsUpdated + onBuiltEvents = dependencies.onEventsUpdated, + onEventsDeleted = dependencies.onEventsDeleted, ) } } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/TimelineChunk.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/TimelineChunk.kt index 4ead1d4ecb..6a5f9da8a6 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/TimelineChunk.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/TimelineChunk.kt @@ -49,21 +49,24 @@ import java.util.concurrent.atomic.AtomicBoolean * It does mainly listen to the db timeline events. * It also triggers pagination to the server when needed, or dispatch to the prev or next chunk if any. */ -internal class TimelineChunk(private val chunkEntity: ChunkEntity, - private val timelineSettings: TimelineSettings, - private val roomId: String, - private val timelineId: String, - private val fetchThreadTimelineTask: FetchThreadTimelineTask, - private val eventDecryptor: TimelineEventDecryptor, - private val paginationTask: PaginationTask, - private val realmConfiguration: RealmConfiguration, - private val fetchTokenAndPaginateTask: FetchTokenAndPaginateTask, - private val timelineEventMapper: TimelineEventMapper, - private val uiEchoManager: UIEchoManager? = null, - private val threadsAwarenessHandler: ThreadsAwarenessHandler, - private val lightweightSettingsStorage: LightweightSettingsStorage, - private val initialEventId: String?, - private val onBuiltEvents: (Boolean) -> Unit) { +internal class TimelineChunk( + private val chunkEntity: ChunkEntity, + private val timelineSettings: TimelineSettings, + private val roomId: String, + private val timelineId: String, + private val fetchThreadTimelineTask: FetchThreadTimelineTask, + private val eventDecryptor: TimelineEventDecryptor, + private val paginationTask: PaginationTask, + private val realmConfiguration: RealmConfiguration, + private val fetchTokenAndPaginateTask: FetchTokenAndPaginateTask, + private val timelineEventMapper: TimelineEventMapper, + private val uiEchoManager: UIEchoManager?, + private val threadsAwarenessHandler: ThreadsAwarenessHandler, + private val lightweightSettingsStorage: LightweightSettingsStorage, + private val initialEventId: String?, + private val onBuiltEvents: (Boolean) -> Unit, + private val onEventsDeleted: () -> Unit, +) { private val isLastForward = AtomicBoolean(chunkEntity.isLastForward) private val isLastBackward = AtomicBoolean(chunkEntity.isLastBackward) @@ -133,6 +136,7 @@ internal class TimelineChunk(private val chunkEntity: ChunkEntity, val prevEvents = prevChunk?.builtItems(includesNext = false, includesPrev = true).orEmpty() deepBuiltItems.addAll(prevEvents) } + return deepBuiltItems } @@ -177,12 +181,14 @@ internal class TimelineChunk(private val chunkEntity: ChunkEntity, val rootThreadEventId = timelineSettings.rootThreadEventId ?: return LoadMoreResult.FAILURE return if (direction == Timeline.Direction.BACKWARDS) { try { - fetchThreadTimelineTask.execute(FetchThreadTimelineTask.Params( - roomId, - rootThreadEventId, - chunkEntity.prevToken, - count - )).toLoadMoreResult() + fetchThreadTimelineTask.execute( + FetchThreadTimelineTask.Params( + roomId, + rootThreadEventId, + chunkEntity.prevToken, + count + ) + ).toLoadMoreResult() } catch (failure: Throwable) { Timber.e(failure, "Failed to fetch thread timeline events from the server") LoadMoreResult.FAILURE @@ -233,13 +239,15 @@ internal class TimelineChunk(private val chunkEntity: ChunkEntity, } /** - * Simple log that displays the number and timeline of loaded events + * Simple log that displays the number and timeline of loaded events. */ private fun logLoadedFromStorage(loadedFromStorage: LoadedFromStorage, direction: Timeline.Direction) { - Timber.v("[" + - "${if (timelineSettings.isThreadTimeline()) "ThreadTimeLine" else "Timeline"}] Has loaded " + - "${loadedFromStorage.numberOfEvents} items from storage in $direction " + - if (timelineSettings.isThreadTimeline() && loadedFromStorage.threadReachedEnd) "[Reached End]" else "") + Timber.v( + "[" + + "${if (timelineSettings.isThreadTimeline()) "ThreadTimeLine" else "Timeline"}] Has loaded " + + "${loadedFromStorage.numberOfEvents} items from storage in $direction " + + if (timelineSettings.isThreadTimeline() && loadedFromStorage.threadReachedEnd) "[Reached End]" else "" + ) } fun getBuiltEventIndex(eventId: String, searchInNext: Boolean, searchInPrev: Boolean): Int? { @@ -358,7 +366,8 @@ internal class TimelineChunk(private val chunkEntity: ChunkEntity, } return LoadedFromStorage( threadReachedEnd = threadReachedEnd(timelineEvents), - numberOfEvents = timelineEvents.size) + numberOfEvents = timelineEvents.size + ) } /** @@ -372,7 +381,7 @@ internal class TimelineChunk(private val chunkEntity: ChunkEntity, /** * This function is responsible to fetch and store the root event of a thread event - * in order to be able to display the event to the user appropriately + * in order to be able to display the event to the user appropriately. */ private suspend fun fetchRootThreadEventsIfNeeded(offsetResults: List) { val eventEntityList = offsetResults @@ -505,6 +514,11 @@ internal class TimelineChunk(private val chunkEntity: ChunkEntity, if (insertions.isNotEmpty() || modifications.isNotEmpty()) { onBuiltEvents(true) } + + val deletions = changeSet.deletions + if (deletions.isNotEmpty()) { + onEventsDeleted() + } } private fun getNextDisplayIndex(direction: Timeline.Direction): Int? { @@ -543,7 +557,8 @@ internal class TimelineChunk(private val chunkEntity: ChunkEntity, threadsAwarenessHandler = threadsAwarenessHandler, lightweightSettingsStorage = lightweightSettingsStorage, initialEventId = null, - onBuiltEvents = this.onBuiltEvents + onBuiltEvents = this.onBuiltEvents, + onEventsDeleted = this.onEventsDeleted ) } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/TimelineEventDataSource.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/TimelineEventDataSource.kt index 638866a46e..8b58d3ca5c 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/TimelineEventDataSource.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/TimelineEventDataSource.kt @@ -19,7 +19,6 @@ package org.matrix.android.sdk.internal.session.room.timeline import androidx.lifecycle.LiveData import com.zhuinden.monarchy.Monarchy import io.realm.Sort -import io.realm.kotlin.where import org.matrix.android.sdk.api.session.events.model.isImageMessage import org.matrix.android.sdk.api.session.events.model.isVideoMessage import org.matrix.android.sdk.api.session.room.timeline.TimelineEvent @@ -29,6 +28,7 @@ import org.matrix.android.sdk.internal.database.mapper.TimelineEventMapper import org.matrix.android.sdk.internal.database.model.TimelineEventEntity import org.matrix.android.sdk.internal.database.model.TimelineEventEntityFields import org.matrix.android.sdk.internal.database.query.where +import org.matrix.android.sdk.internal.database.query.whereRoomId import org.matrix.android.sdk.internal.di.SessionDatabase import org.matrix.android.sdk.internal.task.TaskExecutor import javax.inject.Inject @@ -53,8 +53,7 @@ internal class TimelineEventDataSource @Inject constructor(private val realmSess fun getAttachmentMessages(roomId: String): List { // TODO pretty bad query.. maybe we should denormalize clear type in base? return realmSessionProvider.withRealm { realm -> - realm.where() - .equalTo(TimelineEventEntityFields.ROOM_ID, roomId) + TimelineEventEntity.whereRoomId(realm, roomId) .sort(TimelineEventEntityFields.DISPLAY_INDEX, Sort.ASCENDING) .findAll() ?.mapNotNull { timelineEventMapper.map(it).takeIf { it.root.isImageMessage() || it.root.isVideoMessage() } } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/TimelineEventDecryptor.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/TimelineEventDecryptor.kt index 5c30dc20d9..de79661de0 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/TimelineEventDecryptor.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/TimelineEventDecryptor.kt @@ -99,9 +99,7 @@ internal class TimelineEventDecryptor @Inject constructor( executor?.execute { Realm.getInstance(realmConfiguration).use { realm -> try { - runBlocking { - processDecryptRequest(request, realm) - } + processDecryptRequest(request, realm) } catch (e: InterruptedException) { Timber.i("Decryption got interrupted") } @@ -121,7 +119,7 @@ internal class TimelineEventDecryptor @Inject constructor( } } - private suspend fun processDecryptRequest(request: DecryptionRequest, realm: Realm) { + private fun processDecryptRequest(request: DecryptionRequest, realm: Realm) { val event = request.event val timelineId = request.timelineId @@ -132,7 +130,8 @@ internal class TimelineEventDecryptor @Inject constructor( return } try { - val result = cryptoService.decryptEvent(request.event, timelineId) + // note: runBlocking should be used here while we are in realm single thread executor, to avoid thread switching + val result = runBlocking { cryptoService.decryptEvent(request.event, timelineId) } Timber.v("Successfully decrypted event ${event.eventId}") realm.executeTransaction { val eventId = event.eventId ?: return@executeTransaction diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/TokenChunkEventPersistor.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/TokenChunkEventPersistor.kt index d3f24a8568..96ceb6c6dc 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/TokenChunkEventPersistor.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/TokenChunkEventPersistor.kt @@ -44,6 +44,7 @@ import org.matrix.android.sdk.internal.di.SessionDatabase import org.matrix.android.sdk.internal.di.UserId import org.matrix.android.sdk.internal.session.StreamEventsManager import org.matrix.android.sdk.internal.util.awaitTransaction +import org.matrix.android.sdk.internal.util.time.Clock import timber.log.Timber import javax.inject.Inject @@ -54,7 +55,9 @@ internal class TokenChunkEventPersistor @Inject constructor( @SessionDatabase private val monarchy: Monarchy, @UserId private val userId: String, private val lightweightSettingsStorage: LightweightSettingsStorage, - private val liveEventManager: Lazy) { + private val liveEventManager: Lazy, + private val clock: Clock, +) { enum class Result { SHOULD_FETCH_MORE, @@ -166,7 +169,7 @@ internal class TokenChunkEventPersistor @Inject constructor( val eventList = receivedChunk.events val stateEvents = receivedChunk.stateEvents - val now = System.currentTimeMillis() + val now = clock.epochMillis() stateEvents?.forEach { stateEvent -> val ageLocalTs = stateEvent.unsignedData?.age?.let { now - it } @@ -227,7 +230,8 @@ internal class TokenChunkEventPersistor @Inject constructor( roomId = roomId, eventEntity = eventEntity, direction = direction, - roomMemberContentsByUser = roomMemberContentsByUser) + roomMemberContentsByUser = roomMemberContentsByUser + ) if (lightweightSettingsStorage.areThreadMessagesEnabled()) { eventEntity.rootThreadEventId?.let { // This is a thread event diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/UIEchoManager.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/UIEchoManager.kt index bb92623249..66bb04400b 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/UIEchoManager.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/UIEchoManager.kt @@ -24,10 +24,14 @@ import org.matrix.android.sdk.api.session.room.model.ReactionAggregatedSummary import org.matrix.android.sdk.api.session.room.model.relation.ReactionContent import org.matrix.android.sdk.api.session.room.send.SendState import org.matrix.android.sdk.api.session.room.timeline.TimelineEvent +import org.matrix.android.sdk.internal.util.time.Clock import timber.log.Timber import java.util.Collections -internal class UIEchoManager(private val listener: Listener) { +internal class UIEchoManager( + private val listener: Listener, + private val clock: Clock, +) { interface Listener { fun rebuildEvent(eventId: String, builder: (TimelineEvent) -> TimelineEvent?): Boolean @@ -40,7 +44,7 @@ internal class UIEchoManager(private val listener: Listener) { } /** - * Due to lag of DB updates, we keep some UI echo of some properties to update timeline faster + * Due to lag of DB updates, we keep some UI echo of some properties to update timeline faster. */ private val inMemorySendingStates = Collections.synchronizedMap(HashMap()) @@ -98,9 +102,7 @@ internal class UIEchoManager(private val listener: Listener) { val relatedEventID = timelineEvent.eventId val contents = inMemoryReactions[relatedEventID] ?: return timelineEvent - var existingAnnotationSummary = timelineEvent.annotations ?: EventAnnotationsSummary( - relatedEventID - ) + var existingAnnotationSummary = timelineEvent.annotations ?: EventAnnotationsSummary() val updateReactions = existingAnnotationSummary.reactionsSummary.toMutableList() contents.forEach { uiEchoReaction -> @@ -111,7 +113,7 @@ internal class UIEchoManager(private val listener: Listener) { key = uiEchoReaction.reaction, count = 1, addedByMe = true, - firstTimestamp = System.currentTimeMillis(), + firstTimestamp = clock.epochMillis(), sourceEvents = emptyList(), localEchoEvents = listOf(uiEchoReaction.localEchoId) ).let { updateReactions.add(it) } @@ -145,7 +147,7 @@ internal class UIEchoManager(private val listener: Listener) { fun updateSentStateWithUiEcho(timelineEvent: TimelineEvent): TimelineEvent { if (timelineEvent.root.sendState.isSent()) return timelineEvent val inMemoryState = inMemorySendingStates[timelineEvent.eventId] ?: return timelineEvent - // Timber.v("## ${System.currentTimeMillis()} Send event refresh echo with live state $inMemoryState from state ${element.root.sendState}") + // Timber.v("## ${clock.epochMillis()} Send event refresh echo with live state $inMemoryState from state ${element.root.sendState}") return timelineEvent.copy( root = timelineEvent.root.copyAll() .also { it.sendState = inMemoryState } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/typing/DefaultTypingService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/typing/DefaultTypingService.kt index b76829e893..38ccd19020 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/typing/DefaultTypingService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/typing/DefaultTypingService.kt @@ -31,7 +31,7 @@ import timber.log.Timber * Rules: * - user is typing: notify the homeserver (true), at least once every 10s * - user stop typing: after 10s delay: notify the homeserver (false) - * - user empty the text composer or quit the timeline screen: notify the homeserver (false) + * - user empty the text composer or quit the timeline screen: notify the homeserver (false). */ internal class DefaultTypingService @AssistedInject constructor( @Assisted private val roomId: String, @@ -53,7 +53,7 @@ internal class DefaultTypingService @AssistedInject constructor( private var lastRequestTimestamp: Long = 0 /** - * Notify to the server that the user is typing and schedule the auto typing off + * Notify to the server that the user is typing and schedule the auto typing off. */ override fun userIsTyping() { val now = SystemClock.elapsedRealtime() diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/search/DefaultSearchService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/search/DefaultSearchService.kt index 8033b0654d..12ca36fa6b 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/search/DefaultSearchService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/search/DefaultSearchService.kt @@ -32,15 +32,17 @@ internal class DefaultSearchService @Inject constructor( beforeLimit: Int, afterLimit: Int, includeProfile: Boolean): SearchResult { - return searchTask.execute(SearchTask.Params( - searchTerm = searchTerm, - roomId = roomId, - nextBatch = nextBatch, - orderByRecent = orderByRecent, - limit = limit, - beforeLimit = beforeLimit, - afterLimit = afterLimit, - includeProfile = includeProfile - )) + return searchTask.execute( + SearchTask.Params( + searchTerm = searchTerm, + roomId = roomId, + nextBatch = nextBatch, + orderByRecent = orderByRecent, + limit = limit, + beforeLimit = beforeLimit, + afterLimit = afterLimit, + includeProfile = includeProfile + ) + ) } } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/search/SearchTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/search/SearchTask.kt index 3ba7d11c3d..f785ed4266 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/search/SearchTask.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/search/SearchTask.kt @@ -87,7 +87,7 @@ internal class DefaultSearchTask @Inject constructor( results = searchCategories.roomEvents?.results?.map { searchResponseItem -> val localThreadEventDetails = localTimelineEvents - ?.firstOrNull { it.eventId == searchResponseItem.event.eventId } + ?.firstOrNull { it.eventId == searchResponseItem.event.eventId } ?.root ?.asDomain() ?.threadDetails @@ -112,7 +112,7 @@ internal class DefaultSearchTask @Inject constructor( } /** - * Find local events if exists in order to enhance the result with thread summary + * Find local events if exists in order to enhance the result with thread summary. */ private fun findRootThreadEventsFromDB(searchResponseItemList: List?): List? { return realmSessionProvider.withRealm { realm -> diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/search/request/SearchRequestRoomEvents.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/search/request/SearchRequestRoomEvents.kt index 6064381808..e78c170a32 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/search/request/SearchRequestRoomEvents.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/search/request/SearchRequestRoomEvents.kt @@ -55,7 +55,7 @@ internal data class SearchRequestRoomEvents( * Requests the server return the current state for each room returned. */ @Json(name = "include_state") - val include_state: Boolean? = null + val includeState: Boolean? = null /** * Requests that the server partitions the result set based on the provided list of keys. diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/securestorage/SecretStoringUtils.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/securestorage/SecretStoringUtils.kt index 17dc90fdb0..07a5cbe5a0 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/securestorage/SecretStoringUtils.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/securestorage/SecretStoringUtils.kt @@ -129,7 +129,7 @@ internal class SecretStoringUtils @Inject constructor( } /** - * Decrypt a secret that was encrypted by #securelyStoreString() + * Decrypt a secret that was encrypted by #securelyStoreString(). */ @SuppressLint("NewApi") @Throws(Exception::class) @@ -169,8 +169,10 @@ internal class SecretStoringUtils @Inject constructor( if (secretKeyEntry == null) { // we generate it val generator = KeyGenerator.getInstance(KeyProperties.KEY_ALGORITHM_AES, "AndroidKeyStore") - val keyGenSpec = KeyGenParameterSpec.Builder(alias, - KeyProperties.PURPOSE_ENCRYPT or KeyProperties.PURPOSE_DECRYPT) + val keyGenSpec = KeyGenParameterSpec.Builder( + alias, + KeyProperties.PURPOSE_ENCRYPT or KeyProperties.PURPOSE_DECRYPT + ) .setBlockModes(KeyProperties.BLOCK_MODE_GCM) .setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_NONE) .setKeySize(128) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/space/DefaultSpace.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/space/DefaultSpace.kt index 303eda49d8..178a29a5a0 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/space/DefaultSpace.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/space/DefaultSpace.kt @@ -60,7 +60,7 @@ internal class DefaultSpace( } ?: viaParameterFinder.computeViaParams(roomId, 3)) - room.sendStateEvent( + room.stateService().sendStateEvent( eventType = EventType.STATE_SPACE_CHILD, stateKey = roomId, body = SpaceChildContent( @@ -79,7 +79,7 @@ internal class DefaultSpace( // return // edit state event and set via to null - room.sendStateEvent( + room.stateService().sendStateEvent( eventType = EventType.STATE_SPACE_CHILD, stateKey = roomId, body = SpaceChildContent( @@ -91,19 +91,19 @@ internal class DefaultSpace( } override fun getChildInfo(roomId: String): SpaceChildContent? { - return room.getStateEvents(setOf(EventType.STATE_SPACE_CHILD), QueryStringValue.Equals(roomId)) + return room.stateService().getStateEvents(setOf(EventType.STATE_SPACE_CHILD), QueryStringValue.Equals(roomId)) .firstOrNull() ?.content.toModel() } override suspend fun setChildrenOrder(roomId: String, order: String?) { - val existing = room.getStateEvents(setOf(EventType.STATE_SPACE_CHILD), QueryStringValue.Equals(roomId)) + val existing = room.stateService().getStateEvents(setOf(EventType.STATE_SPACE_CHILD), QueryStringValue.Equals(roomId)) .firstOrNull() ?.content.toModel() ?: throw IllegalArgumentException("$roomId is not a child of this space") // edit state event and set via to null - room.sendStateEvent( + room.stateService().sendStateEvent( eventType = EventType.STATE_SPACE_CHILD, stateKey = roomId, body = SpaceChildContent( @@ -140,7 +140,7 @@ internal class DefaultSpace( // } override suspend fun setChildrenSuggested(roomId: String, suggested: Boolean) { - val existing = room.getStateEvents(setOf(EventType.STATE_SPACE_CHILD), QueryStringValue.Equals(roomId)) + val existing = room.stateService().getStateEvents(setOf(EventType.STATE_SPACE_CHILD), QueryStringValue.Equals(roomId)) .firstOrNull() ?.content.toModel() ?: throw IllegalArgumentException("$roomId is not a child of this space") @@ -150,7 +150,7 @@ internal class DefaultSpace( return } // edit state event and set via to null - room.sendStateEvent( + room.stateService().sendStateEvent( eventType = EventType.STATE_SPACE_CHILD, stateKey = roomId, body = SpaceChildContent( diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/space/DefaultSpaceService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/space/DefaultSpaceService.kt index 05cff7dd89..9320665688 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/space/DefaultSpaceService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/space/DefaultSpaceService.kt @@ -18,12 +18,15 @@ package org.matrix.android.sdk.internal.session.space import android.net.Uri import androidx.lifecycle.LiveData +import kotlinx.coroutines.withContext +import org.matrix.android.sdk.api.MatrixCoroutineDispatchers import org.matrix.android.sdk.api.query.QueryStringValue import org.matrix.android.sdk.api.session.events.model.Event import org.matrix.android.sdk.api.session.events.model.EventType import org.matrix.android.sdk.api.session.events.model.toContent import org.matrix.android.sdk.api.session.events.model.toModel import org.matrix.android.sdk.api.session.room.RoomSortOrder +import org.matrix.android.sdk.api.session.room.getStateEvent import org.matrix.android.sdk.api.session.room.model.GuestAccess import org.matrix.android.sdk.api.session.room.model.Membership import org.matrix.android.sdk.api.session.room.model.PowerLevelsContent @@ -64,7 +67,8 @@ internal class DefaultSpaceService @Inject constructor( private val stateEventDataSource: StateEventDataSource, private val peekSpaceTask: PeekSpaceTask, private val resolveSpaceInfoTask: ResolveSpaceInfoTask, - private val leaveRoomTask: LeaveRoomTask + private val leaveRoomTask: LeaveRoomTask, + private val coroutineDispatchers: MatrixCoroutineDispatchers, ) : SpaceService { override suspend fun createSpace(params: CreateSpaceParams): String { @@ -105,8 +109,10 @@ internal class DefaultSpaceService @Inject constructor( return roomSummaryDataSource.getSpaceSummaries(spaceSummaryQueryParams, sortOrder) } - override fun getRootSpaceSummaries(): List { - return roomSummaryDataSource.getRootSpaceSummaries() + override suspend fun getRootSpaceSummaries(): List { + return withContext(coroutineDispatchers.io) { + roomSummaryDataSource.getRootSpaceSummaries() + } } override suspend fun peekSpace(spaceId: String): SpacePeekResult { @@ -253,7 +259,7 @@ internal class DefaultSpaceService @Inject constructor( val room = roomGetter.getRoom(childRoomId) ?: throw IllegalArgumentException("Unknown Room $childRoomId") - room.sendStateEvent( + room.stateService().sendStateEvent( eventType = EventType.STATE_SPACE_PARENT, stateKey = parentSpaceId, body = SpaceParentContent( @@ -271,7 +277,7 @@ internal class DefaultSpaceService @Inject constructor( if (existingEvent != null) { // Should i check if it was sent by me? // we don't check power level, it will throw if you cannot do that - room.sendStateEvent( + room.stateService().sendStateEvent( eventType = EventType.STATE_SPACE_PARENT, stateKey = parentSpaceId, body = SpaceParentContent( diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/space/JoinSpaceTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/space/JoinSpaceTask.kt index 7eeaed4ff6..a0925cabcc 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/space/JoinSpaceTask.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/space/JoinSpaceTask.kt @@ -49,11 +49,13 @@ internal class DefaultJoinSpaceTask @Inject constructor( override suspend fun execute(params: JoinSpaceTask.Params): JoinSpaceResult { Timber.v("## Space: > Joining root space ${params.roomIdOrAlias} ...") try { - joinRoomTask.execute(JoinRoomTask.Params( - params.roomIdOrAlias, - params.reason, - params.viaServers - )) + joinRoomTask.execute( + JoinRoomTask.Params( + params.roomIdOrAlias, + params.reason, + params.viaServers + ) + ) } catch (failure: Throwable) { return JoinSpaceResult.Fail(failure) } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/space/SpaceApi.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/space/SpaceApi.kt index fda9b4b5bc..d8daa55e15 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/space/SpaceApi.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/space/SpaceApi.kt @@ -40,7 +40,7 @@ internal interface SpaceApi { @Query("from") from: String?): SpacesResponse /** - * Unstable version of [getSpaceHierarchy] + * Unstable version of [getSpaceHierarchy]. */ @GET(NetworkConstants.URI_API_PREFIX_PATH_UNSTABLE + "org.matrix.msc2946/rooms/{roomId}/hierarchy") suspend fun getSpaceHierarchyUnstable( diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/space/SpaceChildSummaryResponse.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/space/SpaceChildSummaryResponse.kt index b6a9c73d36..e3f8977ac5 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/space/SpaceChildSummaryResponse.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/space/SpaceChildSummaryResponse.kt @@ -97,7 +97,7 @@ internal data class SpaceChildSummaryResponse( val avatarUrl: String? = null, /** - * Undocumented item + * Undocumented item. */ @Json(name = "m.federate") val isFederated: Boolean = false diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/InitialSyncStatusRepository.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/InitialSyncStatusRepository.kt index bd20ada28b..e90df3d39d 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/InitialSyncStatusRepository.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/InitialSyncStatusRepository.kt @@ -20,6 +20,7 @@ import com.squareup.moshi.JsonClass import okio.buffer import okio.source import org.matrix.android.sdk.internal.di.MoshiProvider +import org.matrix.android.sdk.internal.util.time.Clock import timber.log.Timber import java.io.File @@ -44,9 +45,12 @@ internal interface InitialSyncStatusRepository { } /** - * This class handle the current status of an initial sync and persist it on the disk, to be robust against crash + * This class handle the current status of an initial sync and persist it on the disk, to be robust against crash. */ -internal class FileInitialSyncStatusRepository(directory: File) : InitialSyncStatusRepository { +internal class FileInitialSyncStatusRepository( + directory: File, + private val clock: Clock, +) : InitialSyncStatusRepository { companion object { // After 2 hours, we consider that the downloaded file is outdated: @@ -64,7 +68,7 @@ internal class FileInitialSyncStatusRepository(directory: File) : InitialSyncSta ensureCache() val state = cache?.step ?: InitialSyncStatus.STEP_INIT return if (state >= InitialSyncStatus.STEP_DOWNLOADED && - System.currentTimeMillis() > (cache?.downloadedDate ?: 0) + INIT_SYNC_FILE_LIFETIME) { + clock.epochMillis() > (cache?.downloadedDate ?: 0) + INIT_SYNC_FILE_LIFETIME) { Timber.d("INIT_SYNC downloaded file is outdated, download it again") // The downloaded file is outdated setStep(InitialSyncStatus.STEP_INIT) @@ -79,7 +83,7 @@ internal class FileInitialSyncStatusRepository(directory: File) : InitialSyncSta if (step == InitialSyncStatus.STEP_DOWNLOADED) { // Also store the downloaded date newStatus = newStatus.copy( - downloadedDate = System.currentTimeMillis() + downloadedDate = clock.epochMillis() ) } cache = newStatus @@ -91,7 +95,7 @@ internal class FileInitialSyncStatusRepository(directory: File) : InitialSyncSta } /** - * File -> Cache + * File -> Cache. */ private fun readFile() { cache = file @@ -100,7 +104,7 @@ internal class FileInitialSyncStatusRepository(directory: File) : InitialSyncSta } /** - * Cache -> File + * Cache -> File. */ private fun writeFile() { file.delete() diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/RoomSyncEphemeralTemporaryStore.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/RoomSyncEphemeralTemporaryStore.kt index ef9f468c86..0e48cddc2d 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/RoomSyncEphemeralTemporaryStore.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/RoomSyncEphemeralTemporaryStore.kt @@ -48,7 +48,7 @@ internal class RoomSyncEphemeralTemporaryStoreFile @Inject constructor( private val roomSyncEphemeralAdapter = moshi.adapter(RoomSyncEphemeral::class.java) /** - * Write RoomSyncEphemeral to a file + * Write RoomSyncEphemeral to a file. */ override fun write(roomId: String, roomSyncEphemeralJson: String) { Timber.w("INIT_SYNC Store ephemeral events for room $roomId") @@ -56,7 +56,7 @@ internal class RoomSyncEphemeralTemporaryStoreFile @Inject constructor( } /** - * Read RoomSyncEphemeral from a file, or null if there is no file to read + * Read RoomSyncEphemeral from a file, or null if there is no file to read. */ override fun read(roomId: String): RoomSyncEphemeral? { return getFile(roomId) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/SyncAPI.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/SyncAPI.kt index 73ec0aa7ef..6c8a71f35e 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/SyncAPI.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/SyncAPI.kt @@ -28,7 +28,7 @@ import retrofit2.http.Streaming internal interface SyncAPI { /** - * Set all the timeouts to 1 minute by default + * Set all the timeouts to 1 minute by default. */ @GET(NetworkConstants.URI_API_PREFIX_PATH_R0 + "sync") suspend fun sync(@QueryMap params: Map, @@ -38,7 +38,7 @@ internal interface SyncAPI { ): SyncResponse /** - * Set all the timeouts to 1 minute by default + * Set all the timeouts to 1 minute by default. */ @Streaming @GET(NetworkConstants.URI_API_PREFIX_PATH_R0 + "sync") diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/SyncPresence.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/SyncPresence.kt index 4f1fe43b7d..ce41a4568c 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/SyncPresence.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/SyncPresence.kt @@ -29,16 +29,19 @@ import org.matrix.android.sdk.api.session.presence.model.PresenceEnum internal enum class SyncPresence(val value: String) { Offline("offline"), Online("online"), + Busy("busy"), Unavailable("unavailable"); companion object { fun from(presenceEnum: PresenceEnum): SyncPresence { return when (presenceEnum) { - PresenceEnum.ONLINE -> Online - PresenceEnum.OFFLINE -> Offline + PresenceEnum.ONLINE -> Online + PresenceEnum.OFFLINE -> Offline + PresenceEnum.BUSY -> Busy PresenceEnum.UNAVAILABLE -> Unavailable } } + fun from(s: String?): SyncPresence? = values().find { it.value == s } } } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/SyncResponseHandler.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/SyncResponseHandler.kt index 97850e81d3..02a7a9a37f 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/SyncResponseHandler.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/SyncResponseHandler.kt @@ -18,9 +18,9 @@ package org.matrix.android.sdk.internal.session.sync import androidx.work.ExistingPeriodicWorkPolicy import com.zhuinden.monarchy.Monarchy -import org.matrix.android.sdk.api.pushrules.PushRuleService -import org.matrix.android.sdk.api.pushrules.RuleScope import org.matrix.android.sdk.api.session.initsync.InitSyncStep +import org.matrix.android.sdk.api.session.pushrules.PushRuleService +import org.matrix.android.sdk.api.session.pushrules.RuleScope import org.matrix.android.sdk.api.session.sync.model.GroupsSyncResponse import org.matrix.android.sdk.api.session.sync.model.RoomsSyncResponse import org.matrix.android.sdk.api.session.sync.model.SyncResponse @@ -34,7 +34,7 @@ import org.matrix.android.sdk.internal.session.dispatchTo import org.matrix.android.sdk.internal.session.group.GetGroupDataWorker import org.matrix.android.sdk.internal.session.initsync.ProgressReporter import org.matrix.android.sdk.internal.session.initsync.reportSubtask -import org.matrix.android.sdk.internal.session.notification.ProcessEventForPushTask +import org.matrix.android.sdk.internal.session.pushrules.ProcessEventForPushTask import org.matrix.android.sdk.internal.session.sync.handler.CryptoSyncHandler import org.matrix.android.sdk.internal.session.sync.handler.GroupSyncHandler import org.matrix.android.sdk.internal.session.sync.handler.PresenceSyncHandler diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/SyncTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/SyncTask.kt index b56f897749..f88d973101 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/SyncTask.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/SyncTask.kt @@ -44,6 +44,7 @@ import org.matrix.android.sdk.internal.session.sync.parsing.InitialSyncResponseP import org.matrix.android.sdk.internal.session.user.UserStore import org.matrix.android.sdk.internal.task.Task import org.matrix.android.sdk.internal.util.logDuration +import org.matrix.android.sdk.internal.util.time.Clock import retrofit2.Response import retrofit2.awaitResponse import timber.log.Timber @@ -78,11 +79,12 @@ internal class DefaultSyncTask @Inject constructor( @SessionFilesDirectory private val fileDirectory: File, private val syncResponseParser: InitialSyncResponseParser, - private val roomSyncEphemeralTemporaryStore: RoomSyncEphemeralTemporaryStore + private val roomSyncEphemeralTemporaryStore: RoomSyncEphemeralTemporaryStore, + private val clock: Clock, ) : SyncTask { private val workingDir = File(fileDirectory, "is") - private val initialSyncStatusRepository: InitialSyncStatusRepository = FileInitialSyncStatusRepository(workingDir) + private val initialSyncStatusRepository: InitialSyncStatusRepository = FileInitialSyncStatusRepository(workingDir, clock) override suspend fun execute(params: SyncTask.Params): SyncResponse { return syncTaskSequencer.post { @@ -107,11 +109,12 @@ internal class DefaultSyncTask @Inject constructor( val isInitialSync = token == null if (isInitialSync) { // We might want to get the user information in parallel too - val user = tryOrNull { session.getProfileAsUser(userId) } + val user = tryOrNull { session.profileService().getProfileAsUser(userId) } userStore.createOrUpdate( userId = userId, displayName = user?.displayName, - avatarUrl = user?.avatarUrl) + avatarUrl = user?.avatarUrl + ) defaultSyncStatusService.startRoot(InitSyncStep.ImportingAccount, 100) } // Maybe refresh the homeserver capabilities data we know @@ -124,7 +127,7 @@ internal class DefaultSyncTask @Inject constructor( if (isInitialSync) { Timber.tag(loggerTag.value).d("INIT_SYNC with filter: ${requestParams["filter"]}") val initSyncStrategy = initialSyncStrategy - logDuration("INIT_SYNC strategy: $initSyncStrategy", loggerTag) { + logDuration("INIT_SYNC strategy: $initSyncStrategy", loggerTag, clock) { if (initSyncStrategy is InitialSyncStrategy.Optimized) { roomSyncEphemeralTemporaryStore.reset() workingDir.mkdirs() @@ -135,7 +138,7 @@ internal class DefaultSyncTask @Inject constructor( // Delete all files workingDir.deleteRecursively() } else { - val syncResponse = logDuration("INIT_SYNC Request", loggerTag) { + val syncResponse = logDuration("INIT_SYNC Request", loggerTag, clock) { executeRequest(globalErrorReceiver) { syncAPI.sync( params = requestParams, @@ -146,7 +149,7 @@ internal class DefaultSyncTask @Inject constructor( // We cannot distinguish request and download in this case. syncStatisticsData.requestInitSyncTime = SystemClock.elapsedRealtime() syncStatisticsData.downloadInitSyncTime = syncStatisticsData.requestInitSyncTime - logDuration("INIT_SYNC Database insertion", loggerTag) { + logDuration("INIT_SYNC Database insertion", loggerTag, clock) { syncResponseHandler.handleResponse(syncResponse, token, defaultSyncStatusService) } syncResponseToReturn = syncResponse @@ -172,12 +175,14 @@ internal class DefaultSyncTask @Inject constructor( val nbToDevice = syncResponse.toDevice?.events.orEmpty().size val nextBatch = syncResponse.nextBatch Timber.tag(loggerTag.value).d( - "Incremental sync request parsing, $nbRooms room(s) $nbToDevice toDevice(s). Got nextBatch: $nextBatch" + "Incremental sync request parsing, $nbRooms room(s) $nbToDevice toDevice(s). Got nextBatch: $nextBatch" + ) + defaultSyncStatusService.setStatus( + SyncStatusService.Status.IncrementalSyncParsing( + rooms = nbRooms, + toDevice = nbToDevice + ) ) - defaultSyncStatusService.setStatus(SyncStatusService.Status.IncrementalSyncParsing( - rooms = nbRooms, - toDevice = nbToDevice - )) syncResponseHandler.handleResponse(syncResponse, token, null) syncResponseToReturn = syncResponse Timber.tag(loggerTag.value).d("Incremental sync done") @@ -201,14 +206,14 @@ internal class DefaultSyncTask @Inject constructor( } } else { initialSyncStatusRepository.setStep(InitialSyncStatus.STEP_DOWNLOADING) - val syncResponse = logDuration("INIT_SYNC Perform server request", loggerTag) { + val syncResponse = logDuration("INIT_SYNC Perform server request", loggerTag, clock) { reportSubtask(defaultSyncStatusService, InitSyncStep.ServerComputing, 1, 0.2f) { getSyncResponse(requestParams, MAX_NUMBER_OF_RETRY_AFTER_TIMEOUT) } } syncStatisticsData.requestInitSyncTime = SystemClock.elapsedRealtime() if (syncResponse.isSuccessful) { - logDuration("INIT_SYNC Download and save to file", loggerTag) { + logDuration("INIT_SYNC Download and save to file", loggerTag, clock) { reportSubtask(defaultSyncStatusService, InitSyncStep.Downloading, 1, 0.1f) { syncResponse.body()?.byteStream()?.use { inputStream -> workingFile.outputStream().use { outputStream -> @@ -247,8 +252,8 @@ internal class DefaultSyncTask @Inject constructor( } private suspend fun handleSyncFile(workingFile: File, initSyncStrategy: InitialSyncStrategy.Optimized): SyncResponse { - return logDuration("INIT_SYNC handleSyncFile()", loggerTag) { - val syncResponse = logDuration("INIT_SYNC Read file and parse", loggerTag) { + return logDuration("INIT_SYNC handleSyncFile()", loggerTag, clock) { + val syncResponse = logDuration("INIT_SYNC Read file and parse", loggerTag, clock) { syncResponseParser.parse(initSyncStrategy, workingFile) } initialSyncStatusRepository.setStep(InitialSyncStatus.STEP_PARSED) @@ -257,7 +262,7 @@ internal class DefaultSyncTask @Inject constructor( val nbOfJoinedRoomsInFile = syncResponse.rooms?.join?.values?.count { it.ephemeral is LazyRoomSyncEphemeral.Stored } Timber.tag(loggerTag.value).d("INIT_SYNC $nbOfJoinedRooms rooms, $nbOfJoinedRoomsInFile ephemeral stored into files") - logDuration("INIT_SYNC Database insertion", loggerTag) { + logDuration("INIT_SYNC Database insertion", loggerTag, clock) { syncResponseHandler.handleResponse(syncResponse, null, defaultSyncStatusService) } initialSyncStatusRepository.setStep(InitialSyncStatus.STEP_SUCCESS) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/handler/CryptoSyncHandler.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/handler/CryptoSyncHandler.kt index 429f498533..dd95762166 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/handler/CryptoSyncHandler.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/handler/CryptoSyncHandler.kt @@ -60,7 +60,7 @@ internal class CryptoSyncHandler @Inject constructor(private val cryptoService: } /** - * Decrypt an encrypted event + * Decrypt an encrypted event. * * @param event the event to decrypt * @param timelineId the timeline identifier diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/handler/PresenceSyncHandler.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/handler/PresenceSyncHandler.kt index 6a7af1dda4..0d4c06339b 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/handler/PresenceSyncHandler.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/handler/PresenceSyncHandler.kt @@ -52,7 +52,7 @@ internal class PresenceSyncHandler @Inject constructor(private val matrixConfigu } /** - * Store user presence to DB and update Direct Rooms and Room Member Summaries accordingly + * Store user presence to DB and update Direct Rooms and Room Member Summaries accordingly. */ private fun storePresenceToDB(realm: Realm, userPresenceEntity: UserPresenceEntity) = realm.copyToRealmOrUpdate(userPresenceEntity)?.apply { diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/handler/SyncResponsePostTreatmentAggregatorHandler.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/handler/SyncResponsePostTreatmentAggregatorHandler.kt index 25de05602a..c638ed4f80 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/handler/SyncResponsePostTreatmentAggregatorHandler.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/handler/SyncResponsePostTreatmentAggregatorHandler.kt @@ -28,6 +28,7 @@ import org.matrix.android.sdk.internal.session.sync.model.accountdata.toMutable import org.matrix.android.sdk.internal.session.user.UserEntityFactory import org.matrix.android.sdk.internal.session.user.accountdata.DirectChatsHelper import org.matrix.android.sdk.internal.session.user.accountdata.UpdateUserAccountDataTask +import org.matrix.android.sdk.internal.util.awaitTransaction import javax.inject.Inject internal class SyncResponsePostTreatmentAggregatorHandler @Inject constructor( @@ -91,9 +92,9 @@ internal class SyncResponsePostTreatmentAggregatorHandler @Inject constructor( } } - private fun List.saveLocally() { + private suspend fun List.saveLocally() { val userEntities = map { user -> UserEntityFactory.create(user) } - monarchy.doWithRealm { + monarchy.awaitTransaction { it.insertOrUpdate(userEntities) } } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/handler/UserAccountDataSyncHandler.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/handler/UserAccountDataSyncHandler.kt index 7f80486c70..c213ea4bcf 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/handler/UserAccountDataSyncHandler.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/handler/UserAccountDataSyncHandler.kt @@ -20,17 +20,19 @@ import com.zhuinden.monarchy.Monarchy import io.realm.Realm import io.realm.RealmList import io.realm.kotlin.where -import org.matrix.android.sdk.api.pushrules.RuleScope -import org.matrix.android.sdk.api.pushrules.RuleSetKey -import org.matrix.android.sdk.api.pushrules.rest.GetPushRulesResponse +import org.matrix.android.sdk.api.failure.GlobalError +import org.matrix.android.sdk.api.failure.InitialSyncRequestReason import org.matrix.android.sdk.api.session.accountdata.UserAccountDataEvent import org.matrix.android.sdk.api.session.accountdata.UserAccountDataTypes import org.matrix.android.sdk.api.session.events.model.Content import org.matrix.android.sdk.api.session.events.model.toModel +import org.matrix.android.sdk.api.session.pushrules.RuleScope +import org.matrix.android.sdk.api.session.pushrules.RuleSetKey import org.matrix.android.sdk.api.session.room.model.RoomMemberContent import org.matrix.android.sdk.api.session.room.model.RoomSummary import org.matrix.android.sdk.api.session.sync.model.InvitedRoomSync import org.matrix.android.sdk.api.session.sync.model.UserAccountDataSync +import org.matrix.android.sdk.internal.SessionManager import org.matrix.android.sdk.internal.database.mapper.ContentMapper import org.matrix.android.sdk.internal.database.mapper.PushRulesMapper import org.matrix.android.sdk.internal.database.mapper.asDomain @@ -39,14 +41,20 @@ import org.matrix.android.sdk.internal.database.model.IgnoredUserEntity import org.matrix.android.sdk.internal.database.model.PushRulesEntity import org.matrix.android.sdk.internal.database.model.RoomSummaryEntity import org.matrix.android.sdk.internal.database.model.RoomSummaryEntityFields +import org.matrix.android.sdk.internal.database.model.TimelineEventEntity import org.matrix.android.sdk.internal.database.model.UserAccountDataEntity import org.matrix.android.sdk.internal.database.model.UserAccountDataEntityFields import org.matrix.android.sdk.internal.database.model.deleteOnCascade +import org.matrix.android.sdk.internal.database.query.findAllFrom import org.matrix.android.sdk.internal.database.query.getDirectRooms import org.matrix.android.sdk.internal.database.query.getOrCreate import org.matrix.android.sdk.internal.database.query.where import org.matrix.android.sdk.internal.di.SessionDatabase +import org.matrix.android.sdk.internal.di.SessionId import org.matrix.android.sdk.internal.di.UserId +import org.matrix.android.sdk.internal.session.SessionListeners +import org.matrix.android.sdk.internal.session.dispatchTo +import org.matrix.android.sdk.internal.session.pushers.GetPushRulesResponse import org.matrix.android.sdk.internal.session.room.RoomAvatarResolver import org.matrix.android.sdk.internal.session.room.membership.RoomDisplayNameResolver import org.matrix.android.sdk.internal.session.room.membership.RoomMemberHelper @@ -65,7 +73,10 @@ internal class UserAccountDataSyncHandler @Inject constructor( private val directChatsHelper: DirectChatsHelper, private val updateUserAccountDataTask: UpdateUserAccountDataTask, private val roomAvatarResolver: RoomAvatarResolver, - private val roomDisplayNameResolver: RoomDisplayNameResolver + private val roomDisplayNameResolver: RoomDisplayNameResolver, + @SessionId private val sessionId: String, + private val sessionManager: SessionManager, + private val sessionListeners: SessionListeners ) { fun handle(realm: Realm, accountData: UserAccountDataSync?) { @@ -184,12 +195,36 @@ internal class UserAccountDataSyncHandler @Inject constructor( private fun handleIgnoredUsers(realm: Realm, event: UserAccountDataEvent) { val userIds = event.content.toModel()?.ignoredUsers?.keys ?: return - realm.where(IgnoredUserEntity::class.java) - .findAll() - .deleteAllFromRealm() + val currentIgnoredUsers = realm.where(IgnoredUserEntity::class.java).findAll() + val currentIgnoredUserIds = currentIgnoredUsers.map { it.userId } + // Delete the previous list + currentIgnoredUsers.deleteAllFromRealm() // And save the new received list userIds.forEach { realm.createObject(IgnoredUserEntity::class.java).apply { userId = it } } - // TODO If not initial sync, we should execute a init sync + + // Delete all the TimelineEvents for all the ignored users + // See https://spec.matrix.org/latest/client-server-api/#client-behaviour-22 : + // "Once ignored, the client will no longer receive events sent by that user, with the exception of state events" + // So just delete all non-state events from our local storage. + TimelineEventEntity.findAllFrom(realm, userIds) + .also { Timber.d("Deleting ${it.size} TimelineEventEntity from ignored users") } + .forEach { + it.deleteOnCascade(true) + } + + // Handle the case when some users are unignored from another session + val mustRefreshCache = currentIgnoredUserIds.any { currentIgnoredUserId -> currentIgnoredUserId !in userIds } + if (mustRefreshCache) { + Timber.d("A user has been unignored from another session, an initial sync should be performed") + dispatchMustRefresh() + } + } + + private fun dispatchMustRefresh() { + val session = sessionManager.getSessionComponent(sessionId)?.session() + session.dispatchTo(sessionListeners) { safeSession, listener -> + listener.onGlobalError(safeSession, GlobalError.InitialSyncRequest(InitialSyncRequestReason.IGNORED_USERS_LIST_CHANGE)) + } } private fun handleBreadcrumbs(realm: Realm, event: UserAccountDataEvent) { diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/handler/room/ReadReceiptHandler.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/handler/room/ReadReceiptHandler.kt index 2c84f1e692..77bee18df9 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/handler/room/ReadReceiptHandler.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/handler/room/ReadReceiptHandler.kt @@ -44,12 +44,14 @@ internal class ReadReceiptHandler @Inject constructor( companion object { - fun createContent(userId: String, eventId: String): ReadReceiptContent { + fun createContent(userId: String, + eventId: String, + currentTimeMillis: Long): ReadReceiptContent { return mapOf( eventId to mapOf( READ_KEY to mapOf( userId to mapOf( - TIMESTAMP_KEY to System.currentTimeMillis().toDouble() + TIMESTAMP_KEY to currentTimeMillis.toDouble() ) ) ) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/handler/room/RoomSyncHandler.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/handler/room/RoomSyncHandler.kt index afd8e1bb99..c5d14afac0 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/handler/room/RoomSyncHandler.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/handler/room/RoomSyncHandler.kt @@ -79,22 +79,26 @@ import org.matrix.android.sdk.internal.session.room.typing.TypingEventContent import org.matrix.android.sdk.internal.session.sync.SyncResponsePostTreatmentAggregator import org.matrix.android.sdk.internal.session.sync.parsing.RoomSyncAccountDataHandler import org.matrix.android.sdk.internal.util.computeBestChunkSize +import org.matrix.android.sdk.internal.util.time.Clock import timber.log.Timber import javax.inject.Inject -internal class RoomSyncHandler @Inject constructor(private val readReceiptHandler: ReadReceiptHandler, - private val roomSummaryUpdater: RoomSummaryUpdater, - private val roomAccountDataHandler: RoomSyncAccountDataHandler, - private val cryptoService: DefaultCryptoService, - private val roomMemberEventHandler: RoomMemberEventHandler, - private val roomTypingUsersHandler: RoomTypingUsersHandler, - private val threadsAwarenessHandler: ThreadsAwarenessHandler, - private val roomChangeMembershipStateDataSource: RoomChangeMembershipStateDataSource, - @UserId private val userId: String, - private val homeServerCapabilitiesService: HomeServerCapabilitiesService, - private val lightweightSettingsStorage: LightweightSettingsStorage, - private val timelineInput: TimelineInput, - private val liveEventService: Lazy) { +internal class RoomSyncHandler @Inject constructor( + private val readReceiptHandler: ReadReceiptHandler, + private val roomSummaryUpdater: RoomSummaryUpdater, + private val roomAccountDataHandler: RoomSyncAccountDataHandler, + private val cryptoService: DefaultCryptoService, + private val roomMemberEventHandler: RoomMemberEventHandler, + private val roomTypingUsersHandler: RoomTypingUsersHandler, + private val threadsAwarenessHandler: ThreadsAwarenessHandler, + private val roomChangeMembershipStateDataSource: RoomChangeMembershipStateDataSource, + @UserId private val userId: String, + private val homeServerCapabilitiesService: HomeServerCapabilitiesService, + private val lightweightSettingsStorage: LightweightSettingsStorage, + private val timelineInput: TimelineInput, + private val liveEventService: Lazy, + private val clock: Clock, +) { sealed class HandlingStrategy { data class JOINED(val data: Map) : HandlingStrategy() @@ -102,11 +106,11 @@ internal class RoomSyncHandler @Inject constructor(private val readReceiptHandle data class LEFT(val data: Map) : HandlingStrategy() } - suspend fun handle(realm: Realm, - roomsSyncResponse: RoomsSyncResponse, - isInitialSync: Boolean, - aggregator: SyncResponsePostTreatmentAggregator, - reporter: ProgressReporter? = null) { + fun handle(realm: Realm, + roomsSyncResponse: RoomsSyncResponse, + isInitialSync: Boolean, + aggregator: SyncResponsePostTreatmentAggregator, + reporter: ProgressReporter? = null) { handleRoomSync(realm, HandlingStrategy.JOINED(roomsSyncResponse.join), isInitialSync, aggregator, reporter) handleRoomSync(realm, HandlingStrategy.INVITED(roomsSyncResponse.invite), isInitialSync, aggregator, reporter) handleRoomSync(realm, HandlingStrategy.LEFT(roomsSyncResponse.leave), isInitialSync, aggregator, reporter) @@ -120,17 +124,17 @@ internal class RoomSyncHandler @Inject constructor(private val readReceiptHandle } // PRIVATE METHODS ***************************************************************************** - private suspend fun handleRoomSync(realm: Realm, - handlingStrategy: HandlingStrategy, - isInitialSync: Boolean, - aggregator: SyncResponsePostTreatmentAggregator, - reporter: ProgressReporter?) { + private fun handleRoomSync(realm: Realm, + handlingStrategy: HandlingStrategy, + isInitialSync: Boolean, + aggregator: SyncResponsePostTreatmentAggregator, + reporter: ProgressReporter?) { val insertType = if (isInitialSync) { EventInsertType.INITIAL_SYNC } else { EventInsertType.INCREMENTAL_SYNC } - val syncLocalTimeStampMillis = System.currentTimeMillis() + val syncLocalTimeStampMillis = clock.epochMillis() val rooms = when (handlingStrategy) { is HandlingStrategy.JOINED -> { if (isInitialSync && initialSyncStrategy is InitialSyncStrategy.Optimized) { @@ -157,11 +161,11 @@ internal class RoomSyncHandler @Inject constructor(private val readReceiptHandle realm.insertOrUpdate(rooms) } - private suspend fun insertJoinRoomsFromInitSync(realm: Realm, - handlingStrategy: HandlingStrategy.JOINED, - syncLocalTimeStampMillis: Long, - aggregator: SyncResponsePostTreatmentAggregator, - reporter: ProgressReporter?) { + private fun insertJoinRoomsFromInitSync(realm: Realm, + handlingStrategy: HandlingStrategy.JOINED, + syncLocalTimeStampMillis: Long, + aggregator: SyncResponsePostTreatmentAggregator, + reporter: ProgressReporter?) { val bestChunkSize = computeBestChunkSize( listSize = handlingStrategy.data.keys.size, limit = (initialSyncStrategy as? InitialSyncStrategy.Optimized)?.maxRoomsToInsert ?: Int.MAX_VALUE @@ -199,17 +203,17 @@ internal class RoomSyncHandler @Inject constructor(private val readReceiptHandle } } - private suspend fun handleJoinedRoom(realm: Realm, - roomId: String, - roomSync: RoomSync, - insertType: EventInsertType, - syncLocalTimestampMillis: Long, - aggregator: SyncResponsePostTreatmentAggregator): RoomEntity { + private fun handleJoinedRoom(realm: Realm, + roomId: String, + roomSync: RoomSync, + insertType: EventInsertType, + syncLocalTimestampMillis: Long, + aggregator: SyncResponsePostTreatmentAggregator): RoomEntity { Timber.v("Handle join sync for room $roomId") val isInitialSync = insertType == EventInsertType.INITIAL_SYNC val ephemeralResult = (roomSync.ephemeral as? LazyRoomSyncEphemeral.Parsed) - ?._roomSyncEphemeral + ?.roomSyncEphemeral ?.events ?.takeIf { it.isNotEmpty() } ?.let { handleEphemeral(realm, roomId, it, insertType == EventInsertType.INITIAL_SYNC, aggregator) } @@ -353,15 +357,15 @@ internal class RoomSyncHandler @Inject constructor(private val readReceiptHandle return roomEntity } - private suspend fun handleTimelineEvents(realm: Realm, - roomId: String, - roomEntity: RoomEntity, - eventList: List, - prevToken: String? = null, - isLimited: Boolean = true, - insertType: EventInsertType, - syncLocalTimestampMillis: Long, - aggregator: SyncResponsePostTreatmentAggregator): ChunkEntity { + private fun handleTimelineEvents(realm: Realm, + roomId: String, + roomEntity: RoomEntity, + eventList: List, + prevToken: String? = null, + isLimited: Boolean = true, + insertType: EventInsertType, + syncLocalTimestampMillis: Long, + aggregator: SyncResponsePostTreatmentAggregator): ChunkEntity { val lastChunk = ChunkEntity.findLastForwardChunkOfRoom(realm, roomEntity.roomId) if (isLimited && lastChunk != null) { lastChunk.deleteOnCascade(deleteStateEvents = false, canDeleteRoot = true) @@ -378,7 +382,10 @@ internal class RoomSyncHandler @Inject constructor(private val readReceiptHandle val roomMemberContentsByUser = HashMap() val optimizedThreadSummaryMap = hashMapOf() - for (event in eventList) { + for (rawEvent in eventList) { + // It's annoying roomId is not there, but lot of code rely on it. + // And had to do it now as copy would delete all decryption results.. + val event = rawEvent.copy(roomId = roomId) if (event.eventId == null || event.senderId == null || event.type == null) { continue } @@ -389,8 +396,10 @@ internal class RoomSyncHandler @Inject constructor(private val readReceiptHandle liveEventService.get().dispatchLiveEventReceived(event, roomId, isInitialSync) if (event.isEncrypted() && !isInitialSync) { - runBlocking { + try { decryptIfNeeded(event, roomId) + } catch (e: InterruptedException) { + Timber.i("Decryption got interrupted") } } var contentToInject: String? = null @@ -421,7 +430,8 @@ internal class RoomSyncHandler @Inject constructor(private val readReceiptHandle roomId = roomId, eventEntity = eventEntity, direction = PaginationDirection.FORWARDS, - roomMemberContentsByUser = roomMemberContentsByUser) + roomMemberContentsByUser = roomMemberContentsByUser + ) if (lightweightSettingsStorage.areThreadMessagesEnabled()) { eventEntity.rootThreadEventId?.let { // This is a thread event @@ -437,7 +447,9 @@ internal class RoomSyncHandler @Inject constructor(private val readReceiptHandle threadEventEntity = eventEntity, roomMemberContentsByUser = roomMemberContentsByUser, userId = userId, - roomEntity = roomEntity) + roomEntity = roomEntity, + currentTimeMillis = clock.epochMillis(), + ) } } ?: run { // This is a normal event or a root thread one @@ -445,7 +457,7 @@ internal class RoomSyncHandler @Inject constructor(private val readReceiptHandle } } // Give info to crypto module - cryptoService.onLiveEvent(roomEntity.roomId, event) + cryptoService.onLiveEvent(roomEntity.roomId, event, isInitialSync) // Try to remove local echo event.unsignedData?.transactionId?.also { @@ -475,7 +487,8 @@ internal class RoomSyncHandler @Inject constructor(private val readReceiptHandle roomId = roomId, realm = realm, chunkEntity = chunkEntity, - currentUserId = userId) + currentUserId = userId + ) } // posting new events to timeline if any is registered @@ -505,10 +518,11 @@ internal class RoomSyncHandler @Inject constructor(private val readReceiptHandle } } - private suspend fun decryptIfNeeded(event: Event, roomId: String) { + private fun decryptIfNeeded(event: Event, roomId: String) { try { // Event from sync does not have roomId, so add it to the event first - val result = cryptoService.decryptEvent(event.copy(roomId = roomId), "") + // note: runBlocking should be used here while we are in realm single thread executor, to avoid thread switching + val result = runBlocking { cryptoService.decryptEvent(event.copy(roomId = roomId), "") } event.mxDecryptionResult = OlmDecryptionResult( payload = result.clearEvent, senderKey = result.senderCurve25519Key, diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/handler/room/ThreadsAwarenessHandler.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/handler/room/ThreadsAwarenessHandler.kt index efc8e39a18..03e076c217 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/handler/room/ThreadsAwarenessHandler.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/handler/room/ThreadsAwarenessHandler.kt @@ -73,7 +73,7 @@ internal class ThreadsAwarenessHandler @Inject constructor( private val cacheEventRootId = hashSetOf() /** - * Fetch root thread events if they are missing from the local storage + * Fetch root thread events if they are missing from the local storage. * @param syncResponse the sync response */ suspend fun fetchRootThreadEventsIfNeeded(syncResponse: SyncResponse) { @@ -92,7 +92,7 @@ internal class ThreadsAwarenessHandler @Inject constructor( } /** - * Fetch root thread events if they are missing from the local storage + * Fetch root thread events if they are missing from the local storage. * @param eventList a list with the events to examine */ suspend fun fetchRootThreadEventsIfNeeded(eventList: List) { @@ -115,7 +115,7 @@ internal class ThreadsAwarenessHandler @Inject constructor( } /** - * Fetch multiple unique events using the fetchEvent function + * Fetch multiple unique events using the fetchEvent function. */ private suspend fun fetchThreadsEvents(threadsToFetch: Map) { val eventEntityList = threadsToFetch.mapNotNull { (eventId, roomId) -> @@ -153,7 +153,7 @@ internal class ThreadsAwarenessHandler @Inject constructor( } /** - * Handle events mainly coming from the RoomSyncHandler + * Handle events mainly coming from the RoomSyncHandler. * @return The content to inject in the roomSyncHandler live events */ fun makeEventThreadAware(realm: Realm, @@ -186,7 +186,8 @@ internal class ThreadsAwarenessHandler @Inject constructor( eventBody = eventBody, eventToInject = eventToInject, eventToInjectBody = eventToInjectBody, - threadRelation = threadRelation) ?: return null + threadRelation = threadRelation + ) ?: return null // update the event contentForNonEncrypted = updateEventEntity(event, eventEntity, eventPayload, messageTextContent) @@ -225,9 +226,9 @@ internal class ThreadsAwarenessHandler @Inject constructor( } /** - * This function is responsible to check if there is any event that relates to our current event + * This function is responsible to check if there is any event that relates to our current event. * This is useful when we receive an event that relates to a missing parent, so when later we receive the parent - * we can update the child as well + * we can update the child as well. * @param event the current event that we examine * @param eventBody the current body of the event * @param isFromCache determines whether or not we already know this is root thread event @@ -253,7 +254,8 @@ internal class ThreadsAwarenessHandler @Inject constructor( eventBody = newEventBody, eventToInject = event, eventToInjectBody = eventBody, - threadRelation = threadRelation) ?: return null + threadRelation = threadRelation + ) ?: return null return updateEventEntity(newEventFound, eventEntityFound, newEventPayload, messageTextContent) } @@ -261,7 +263,7 @@ internal class ThreadsAwarenessHandler @Inject constructor( } /** - * Actual update the eventEntity with the new payload + * Actual update the eventEntity with the new payload. * @return the content to inject when this is executed by RoomSyncHandler */ private fun updateEventEntity(event: Event, @@ -289,7 +291,7 @@ internal class ThreadsAwarenessHandler @Inject constructor( } /** - * Injecting $eventToInject decrypted content as a reply to $event + * Injecting $eventToInject decrypted content as a reply to $event. * @param eventToInject the event that will inject * @param eventBody the actual event body * @return The final content with the injected event @@ -309,7 +311,8 @@ internal class ThreadsAwarenessHandler @Inject constructor( userLink, eventIdToInjectSenderId, eventToInjectBody, - eventBody) + eventBody + ) return MessageTextContent( relatesTo = threadRelation, @@ -321,7 +324,7 @@ internal class ThreadsAwarenessHandler @Inject constructor( } /** - * Integrate fallback Quote reply + * Integrate fallback Quote reply. */ private fun injectFallbackIndicator(event: Event, eventBody: String, @@ -330,7 +333,8 @@ internal class ThreadsAwarenessHandler @Inject constructor( threadRelation: RelationDefaultContent?): String? { val replyFormatted = LocalEchoEventFactory.QUOTE_PATTERN.format( "In reply to a thread", - eventBody) + eventBody + ) val messageTextContent = MessageTextContent( relatesTo = threadRelation, @@ -360,7 +364,7 @@ internal class ThreadsAwarenessHandler @Inject constructor( /** * Try to get the event form the local DB, if the event does not exist null - * will be returned + * will be returned. */ private fun getEventFromDB(realm: Realm, eventId: String): Event? { val eventEntity = EventEntity.where(realm, eventId = eventId).findFirst() ?: return null @@ -368,14 +372,14 @@ internal class ThreadsAwarenessHandler @Inject constructor( } /** - * Returns True if the event is a thread + * Returns True if the event is a thread. * @param event */ private fun isThreadEvent(event: Event): Boolean = event.content.toModel()?.relatesTo?.type == RelationType.THREAD /** - * Returns the root thread eventId or null otherwise + * Returns the root thread eventId or null otherwise. * @param event */ private fun getRootThreadEventId(event: Event): String? = diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/job/SyncWorker.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/job/SyncWorker.kt index c67c0e350e..bbb18b664c 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/job/SyncWorker.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/job/SyncWorker.kt @@ -38,11 +38,11 @@ private const val DEFAULT_LONG_POOL_TIMEOUT_SECONDS = 6L private const val DEFAULT_DELAY_MILLIS = 30_000L /** - * Possible previous worker: None - * Possible next worker : None + * Possible previous worker: None. + * Possible next worker : None. */ internal class SyncWorker(context: Context, workerParameters: WorkerParameters, sessionManager: SessionManager) : - SessionSafeCoroutineWorker(context, workerParameters, sessionManager, Params::class.java) { + SessionSafeCoroutineWorker(context, workerParameters, sessionManager, Params::class.java) { @JsonClass(generateAdapter = true) internal data class Params( diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/model/DeviceInfo.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/model/DeviceInfo.kt index fdb9916190..7f6759906f 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/model/DeviceInfo.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/model/DeviceInfo.kt @@ -19,24 +19,24 @@ import com.squareup.moshi.Json import com.squareup.moshi.JsonClass /** - * This class describes the device information + * This class describes the device information. */ @JsonClass(generateAdapter = true) internal data class DeviceInfo( /** - * The owner user id + * The owner user id. */ @Json(name = "user_id") val userId: String? = null, /** - * The device id + * The device id. */ @Json(name = "device_id") val deviceId: String? = null, /** - * The device display name + * The device display name. */ @Json(name = "display_name") val displayName: String? = null, @@ -48,7 +48,7 @@ internal data class DeviceInfo( val lastSeenTs: Long = 0, /** - * The last ip address + * The last ip address. */ @Json(name = "last_seen_ip") val lastSeenIp: String? = null diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/model/DevicesListResponse.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/model/DevicesListResponse.kt index 3dc71a355d..acef22a542 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/model/DevicesListResponse.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/model/DevicesListResponse.kt @@ -17,9 +17,6 @@ package org.matrix.android.sdk.internal.session.sync.model import com.squareup.moshi.JsonClass -/** - * This class describes the - */ @JsonClass(generateAdapter = true) internal data class DevicesListResponse( val devices: List? = null diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/model/accountdata/DirectMessagesContent.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/model/accountdata/DirectMessagesContent.kt index 7c73f1fed0..2e45b2ad98 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/model/accountdata/DirectMessagesContent.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/model/accountdata/DirectMessagesContent.kt @@ -17,7 +17,7 @@ package org.matrix.android.sdk.internal.session.sync.model.accountdata /** - * Keys are userIds, values are list of roomIds + * Keys are userIds, values are list of roomIds. */ internal typealias DirectMessagesContent = Map> diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/terms/AcceptTermsBody.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/terms/AcceptTermsBody.kt index ee23c9e6bc..bf954b0aee 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/terms/AcceptTermsBody.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/terms/AcceptTermsBody.kt @@ -20,7 +20,7 @@ import com.squareup.moshi.Json import com.squareup.moshi.JsonClass /** - * This class represent a list of urls of terms the user wants to accept + * This class represent a list of urls of terms the user wants to accept. */ @JsonClass(generateAdapter = true) internal data class AcceptTermsBody( diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/terms/DefaultTermsService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/terms/DefaultTermsService.kt index 5f62f40ab3..9876643bed 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/terms/DefaultTermsService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/terms/DefaultTermsService.kt @@ -61,7 +61,7 @@ internal class DefaultTermsService @Inject constructor( } /** - * We use a trick here to get the homeserver T&C, we use the register API + * We use a trick here to get the homeserver T&C, we use the register API. */ override suspend fun getHomeserverTerms(baseUrl: String): TermsResponse { return try { @@ -108,9 +108,11 @@ internal class DefaultTermsService @Inject constructor( val newList = listOfAcceptedTerms.toMutableSet().apply { addAll(agreedUrls) }.toList() - updateUserAccountDataTask.execute(UpdateUserAccountDataTask.AcceptedTermsParams( - acceptedTermsContent = AcceptedTermsContent(newList) - )) + updateUserAccountDataTask.execute( + UpdateUserAccountDataTask.AcceptedTermsParams( + acceptedTermsContent = AcceptedTermsContent(newList) + ) + ) } private suspend fun getToken(url: String): String { diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/terms/TermsAPI.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/terms/TermsAPI.kt index 1f117de67e..f6f57bf0ef 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/terms/TermsAPI.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/terms/TermsAPI.kt @@ -28,13 +28,13 @@ import retrofit2.http.Url internal interface TermsAPI { /** - * This request does not require authentication + * This request does not require authentication. */ @GET suspend fun getTerms(@Url url: String): TermsResponse /** - * This request requires authentication + * This request requires authentication. */ @POST suspend fun agreeToTerms(@Url url: String, @@ -43,7 +43,7 @@ internal interface TermsAPI { /** * API to retrieve the terms for a homeserver. The API /terms does not exist yet, so retrieve the terms from the login flow. - * We do not care about the result (Credentials) + * We do not care about the result (Credentials). */ @POST suspend fun register(@Url url: String, diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/thirdparty/DefaultThirdPartyService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/thirdparty/DefaultThirdPartyService.kt index fdd5524fc2..210cb192e7 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/thirdparty/DefaultThirdPartyService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/thirdparty/DefaultThirdPartyService.kt @@ -23,7 +23,7 @@ import javax.inject.Inject internal class DefaultThirdPartyService @Inject constructor(private val getThirdPartyProtocolTask: GetThirdPartyProtocolsTask, private val getThirdPartyUserTask: GetThirdPartyUserTask) : - ThirdPartyService { + ThirdPartyService { override suspend fun getThirdPartyProtocols(): Map { return getThirdPartyProtocolTask.execute(Unit) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/typing/DefaultTypingUsersTracker.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/typing/DefaultTypingUsersTracker.kt index c5c3fc4b59..ac4a886aa3 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/typing/DefaultTypingUsersTracker.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/typing/DefaultTypingUsersTracker.kt @@ -27,7 +27,7 @@ internal class DefaultTypingUsersTracker @Inject constructor() : TypingUsersTrac private val typingUsers = mutableMapOf>() /** - * Set all currently typing users for a room (excluding yourself) + * Set all currently typing users for a room (excluding yourself). */ fun setTypingUsersFromRoom(roomId: String, senderInfoList: List) { val hasNewValue = typingUsers[roomId] != senderInfoList diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/user/UserModule.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/user/UserModule.kt index 4dfc7586ae..c205c4f1c6 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/user/UserModule.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/user/UserModule.kt @@ -21,9 +21,7 @@ import dagger.Module import dagger.Provides import org.matrix.android.sdk.api.session.user.UserService import org.matrix.android.sdk.internal.session.SessionScope -import org.matrix.android.sdk.internal.session.user.accountdata.DefaultSaveIgnoredUsersTask import org.matrix.android.sdk.internal.session.user.accountdata.DefaultUpdateIgnoredUserIdsTask -import org.matrix.android.sdk.internal.session.user.accountdata.SaveIgnoredUsersTask import org.matrix.android.sdk.internal.session.user.accountdata.UpdateIgnoredUserIdsTask import org.matrix.android.sdk.internal.session.user.model.DefaultSearchUserTask import org.matrix.android.sdk.internal.session.user.model.SearchUserTask @@ -48,9 +46,6 @@ internal abstract class UserModule { @Binds abstract fun bindSearchUserTask(task: DefaultSearchUserTask): SearchUserTask - @Binds - abstract fun bindSaveIgnoredUsersTask(task: DefaultSaveIgnoredUsersTask): SaveIgnoredUsersTask - @Binds abstract fun bindUpdateIgnoredUserIdsTask(task: DefaultUpdateIgnoredUserIdsTask): UpdateIgnoredUserIdsTask diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/user/accountdata/AccountDataContent.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/user/accountdata/AccountDataContent.kt index 5f9f0777d8..c3daa2a0e9 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/user/accountdata/AccountDataContent.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/user/accountdata/AccountDataContent.kt @@ -17,6 +17,6 @@ package org.matrix.android.sdk.internal.session.user.accountdata /** - * Tag class to identify every account data content + * Tag class to identify every account data content. */ internal interface AccountDataContent diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/user/accountdata/DefaultSessionAccountDataService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/user/accountdata/DefaultSessionAccountDataService.kt index 59c4dd671e..df9dcfb903 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/user/accountdata/DefaultSessionAccountDataService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/user/accountdata/DefaultSessionAccountDataService.kt @@ -68,7 +68,7 @@ internal class DefaultSessionAccountDataService @Inject constructor( val params = UpdateUserAccountDataTask.AnyParams(type = type, any = content) awaitCallback { callback -> updateUserAccountDataTask.configureWith(params) { - this.retryCount = 5 // TODO: Need to refactor retrying out into a helper method. + this.retryCount = 5 // TODO Need to refactor retrying out into a helper method. this.callback = callback } .executeBy(taskExecutor) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/user/accountdata/SaveBreadcrumbsTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/user/accountdata/SaveBreadcrumbsTask.kt index 22af56a169..148f9d657d 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/user/accountdata/SaveBreadcrumbsTask.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/user/accountdata/SaveBreadcrumbsTask.kt @@ -29,7 +29,7 @@ import org.matrix.android.sdk.internal.util.awaitTransaction import javax.inject.Inject /** - * Save the Breadcrumbs roomId list in DB, either from the sync, or updated locally + * Save the Breadcrumbs roomId list in DB, either from the sync, or updated locally. */ internal interface SaveBreadcrumbsTask : Task { data class Params( diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/user/accountdata/SaveIgnoredUsersTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/user/accountdata/SaveIgnoredUsersTask.kt deleted file mode 100644 index 63c0ce645e..0000000000 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/user/accountdata/SaveIgnoredUsersTask.kt +++ /dev/null @@ -1,47 +0,0 @@ -/* - * Copyright 2020 The Matrix.org Foundation C.I.C. - * - * 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 org.matrix.android.sdk.internal.session.user.accountdata - -import com.zhuinden.monarchy.Monarchy -import org.matrix.android.sdk.internal.database.model.IgnoredUserEntity -import org.matrix.android.sdk.internal.di.SessionDatabase -import org.matrix.android.sdk.internal.task.Task -import org.matrix.android.sdk.internal.util.awaitTransaction -import javax.inject.Inject - -/** - * Save the ignored users list in DB - */ -internal interface SaveIgnoredUsersTask : Task { - data class Params( - val userIds: List - ) -} - -internal class DefaultSaveIgnoredUsersTask @Inject constructor(@SessionDatabase private val monarchy: Monarchy) : SaveIgnoredUsersTask { - - override suspend fun execute(params: SaveIgnoredUsersTask.Params) { - monarchy.awaitTransaction { realm -> - // clear current ignored users - realm.where(IgnoredUserEntity::class.java) - .findAll() - .deleteAllFromRealm() - - // And save the new received list - params.userIds.forEach { realm.createObject(IgnoredUserEntity::class.java).apply { userId = it } } - } - } -} diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/user/accountdata/UpdateBreadcrumbsTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/user/accountdata/UpdateBreadcrumbsTask.kt index 874c2741de..c4ea029cbb 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/user/accountdata/UpdateBreadcrumbsTask.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/user/accountdata/UpdateBreadcrumbsTask.kt @@ -60,8 +60,10 @@ internal class DefaultUpdateBreadcrumbsTask @Inject constructor( // FIXME It can remove the previous breadcrumbs, if not synced yet // And update account data - updateUserAccountDataTask.execute(UpdateUserAccountDataTask.BreadcrumbsParams( - breadcrumbsContent = BreadcrumbsContent(newBreadcrumbs) - )) + updateUserAccountDataTask.execute( + UpdateUserAccountDataTask.BreadcrumbsParams( + breadcrumbsContent = BreadcrumbsContent(newBreadcrumbs) + ) + ) } } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/user/accountdata/UpdateIgnoredUserIdsTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/user/accountdata/UpdateIgnoredUserIdsTask.kt index 445b78104c..173161f8ae 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/user/accountdata/UpdateIgnoredUserIdsTask.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/user/accountdata/UpdateIgnoredUserIdsTask.kt @@ -38,7 +38,6 @@ internal interface UpdateIgnoredUserIdsTask : Task { +internal interface CreateWidgetTask : Task { data class Params( val roomId: String, @@ -45,8 +46,8 @@ internal class DefaultCreateWidgetTask @Inject constructor(@SessionDatabase priv @UserId private val userId: String, private val globalErrorReceiver: GlobalErrorReceiver) : CreateWidgetTask { - override suspend fun execute(params: CreateWidgetTask.Params) { - executeRequest(globalErrorReceiver) { + override suspend fun execute(params: CreateWidgetTask.Params): String { + val response = executeRequest(globalErrorReceiver) { roomAPI.sendStateEvent( roomId = params.roomId, stateEventType = EventType.STATE_ROOM_WIDGET_LEGACY, @@ -60,5 +61,8 @@ internal class DefaultCreateWidgetTask @Inject constructor(@SessionDatabase priv .and() .equalTo(CurrentStateEventEntityFields.ROOT.SENDER, userId) } + return response.eventId.also { + Timber.d("Widget state event: $it just sent in room ${params.roomId}") + } } } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/widgets/DefaultWidgetPostAPIMediator.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/widgets/DefaultWidgetPostAPIMediator.kt index 10b4f7f7d7..1da6827916 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/widgets/DefaultWidgetPostAPIMediator.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/widgets/DefaultWidgetPostAPIMediator.kt @@ -29,7 +29,7 @@ import javax.inject.Inject internal class DefaultWidgetPostAPIMediator @Inject constructor(private val moshi: Moshi, private val widgetPostMessageAPIProvider: WidgetPostMessageAPIProvider) : - WidgetPostAPIMediator { + WidgetPostAPIMediator { private val jsonAdapter = moshi.adapter(JSON_DICT_PARAMETERIZED_TYPE) @@ -93,7 +93,7 @@ internal class DefaultWidgetPostAPIMediator @Inject constructor(private val mosh */ /** - * Send a boolean response + * Send a boolean response. * * @param response the response * @param eventData the modular data @@ -104,7 +104,7 @@ internal class DefaultWidgetPostAPIMediator @Inject constructor(private val mosh } /** - * Send an integer response + * Send an integer response. * * @param response the response * @param eventData the modular data @@ -114,7 +114,7 @@ internal class DefaultWidgetPostAPIMediator @Inject constructor(private val mosh } /** - * Send an object response + * Send an object response. * * @param response the response * @param eventData the modular data @@ -133,7 +133,7 @@ internal class DefaultWidgetPostAPIMediator @Inject constructor(private val mosh } /** - * Send success + * Send success. * * @param eventData the modular data */ @@ -143,7 +143,7 @@ internal class DefaultWidgetPostAPIMediator @Inject constructor(private val mosh } /** - * Send an error + * Send an error. * * @param message the error message * @param eventData the modular data @@ -151,7 +151,7 @@ internal class DefaultWidgetPostAPIMediator @Inject constructor(private val mosh override fun sendError(message: String, eventData: JsonDict) { Timber.e("## sendError() : eventData $eventData failed $message") - // TODO: JS has an additional optional parameter: nestedError + // TODO JS has an additional optional parameter: nestedError val params = HashMap>() val subMap = HashMap() subMap["message"] = message @@ -160,7 +160,7 @@ internal class DefaultWidgetPostAPIMediator @Inject constructor(private val mosh } /** - * Send the response to the javascript + * Send the response to the javascript. * * @param jsString the response data * @param eventData the modular data diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/widgets/DefaultWidgetService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/widgets/DefaultWidgetService.kt index 89e827aea0..53a435d217 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/widgets/DefaultWidgetService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/widgets/DefaultWidgetService.kt @@ -29,7 +29,7 @@ import javax.inject.Provider internal class DefaultWidgetService @Inject constructor(private val widgetManager: WidgetManager, private val widgetURLFormatter: WidgetURLFormatter, private val widgetPostAPIMediator: Provider) : - WidgetService { + WidgetService { override fun getWidgetURLFormatter(): WidgetURLFormatter { return widgetURLFormatter @@ -52,7 +52,7 @@ internal class DefaultWidgetService @Inject constructor(private val widgetManage return widgetManager.getWidgetComputedUrl(widget, isLightTheme) } -override fun getRoomWidgetsLive( + override fun getRoomWidgetsLive( roomId: String, widgetId: QueryStringValue, widgetTypes: Set?, diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/widgets/WidgetManager.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/widgets/WidgetManager.kt index e18117d2d7..07ed91c179 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/widgets/WidgetManager.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/widgets/WidgetManager.kt @@ -52,7 +52,7 @@ internal class WidgetManager @Inject constructor(private val integrationManager: private val widgetFactory: WidgetFactory, @UserId private val userId: String) : - IntegrationManagerService.Listener, SessionLifecycleObserver { + IntegrationManagerService.Listener, SessionLifecycleObserver { private val lifecycleOwner: LifecycleOwner = LifecycleOwner { lifecycleRegistry } private val lifecycleRegistry: LifecycleRegistry = LifecycleRegistry(lifecycleOwner) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/widgets/WidgetsAPI.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/widgets/WidgetsAPI.kt index bfc243c213..b871a317c8 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/widgets/WidgetsAPI.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/widgets/WidgetsAPI.kt @@ -24,7 +24,7 @@ import retrofit2.http.Query internal interface WidgetsAPI { /** - * register to the server + * Register to the server. * * @param body the body content (Ref: https://github.com/matrix-org/matrix-doc/pull/1961) */ diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/util/Base64.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/util/Base64.kt index aae8bf1967..ea7d75a42c 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/util/Base64.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/util/Base64.kt @@ -17,7 +17,7 @@ package org.matrix.android.sdk.internal.util /** - * Base64 URL conversion methods + * Base64 URL conversion methods. */ internal fun base64UrlToBase64(base64Url: String): String { diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/util/MathUtils.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/util/BestChunkSize.kt similarity index 100% rename from matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/util/MathUtils.kt rename to matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/util/BestChunkSize.kt diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/util/CancelableCoroutine.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/util/CancelableCoroutine.kt index f398ee25d8..74b54943cd 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/util/CancelableCoroutine.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/util/CancelableCoroutine.kt @@ -24,7 +24,7 @@ internal fun Job.toCancelable(): Cancelable { } /** - * Private, use the extension above + * Private, use the extension above. */ private class CancelableCoroutine(private val job: Job) : Cancelable { diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/util/FailureExt.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/util/FailureExt.kt index 8c78feeac3..0d65555707 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/util/FailureExt.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/util/FailureExt.kt @@ -22,7 +22,7 @@ import org.matrix.android.sdk.api.failure.MatrixError import org.matrix.android.sdk.internal.di.MoshiProvider /** - * Try to extract and serialize a MatrixError, or default to localizedMessage + * Try to extract and serialize a MatrixError, or default to localizedMessage. */ internal fun Throwable.toMatrixErrorStr(): String { return (this as? Failure.ServerError) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/util/FileSaver.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/util/FileSaver.kt index 3fcf35c127..1bd704a9e8 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/util/FileSaver.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/util/FileSaver.kt @@ -21,7 +21,7 @@ import java.io.File import java.io.InputStream /** - * Save an input stream to a file with Okio + * Save an input stream to a file with Okio. */ @WorkerThread internal fun writeToFile(inputStream: InputStream, outputFile: File) { diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/util/JsonCanonicalizer.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/util/JsonCanonicalizer.kt index 5994cbcf93..94aa238789 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/util/JsonCanonicalizer.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/util/JsonCanonicalizer.kt @@ -51,7 +51,7 @@ internal object JsonCanonicalizer { } /** - * Canonicalize a JSON element + * Canonicalize a JSON element. * * @param src the src * @return the canonicalize element diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/util/LogUtil.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/util/LogUtil.kt index 6fd907d397..ffad0b856c 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/util/LogUtil.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/util/LogUtil.kt @@ -18,6 +18,7 @@ package org.matrix.android.sdk.internal.util import org.matrix.android.sdk.BuildConfig import org.matrix.android.sdk.api.logger.LoggerTag +import org.matrix.android.sdk.internal.util.time.Clock import timber.log.Timber internal fun Collection.logLimit(maxQuantity: Int = 5): String { @@ -34,13 +35,14 @@ internal fun Collection.logLimit(maxQuantity: Int = 5): String { internal suspend fun logDuration(message: String, loggerTag: LoggerTag, + clock: Clock, block: suspend () -> T): T { Timber.tag(loggerTag.value).d("$message -- BEGIN") - val start = System.currentTimeMillis() + val start = clock.epochMillis() val result = logRamUsage(message) { block() } - val duration = System.currentTimeMillis() - start + val duration = clock.epochMillis() - start Timber.tag(loggerTag.value).d("$message -- END duration: $duration ms") return result diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/util/SecretKeyAndVersion.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/util/SecretKeyAndVersion.kt index 40aead5d63..79a1554610 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/util/SecretKeyAndVersion.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/util/SecretKeyAndVersion.kt @@ -19,14 +19,15 @@ package org.matrix.android.sdk.internal.util import javax.crypto.SecretKey /** - * Tuple which contains the secret key and the version of Android when the key has been generated + * Tuple which contains the secret key and the version of Android when the key has been generated. */ internal data class SecretKeyAndVersion( /** - * the key + * the key. */ val secretKey: SecretKey, /** - * The android version when the key has been generated + * The android version when the key has been generated. */ - val androidVersionWhenTheKeyHasBeenGenerated: Int) + val androidVersionWhenTheKeyHasBeenGenerated: Int +) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/util/StringUtils.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/util/StringUtils.kt index 8a6ec18986..d9fd312a6f 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/util/StringUtils.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/util/StringUtils.kt @@ -20,7 +20,7 @@ import timber.log.Timber import java.util.Locale /** - * Convert a string to an UTF8 String + * Convert a string to an UTF8 String. * * @param s the string to convert * @return the utf-8 string @@ -36,7 +36,7 @@ internal fun convertToUTF8(s: String): String { } /** - * Convert a string from an UTF8 String + * Convert a string from an UTF8 String. * * @param s the string to convert * @return the utf-16 string @@ -76,7 +76,7 @@ internal fun String.caseInsensitiveFind(subString: String): Boolean { internal val spaceChars = "[\u00A0\u2000-\u200B\u2800\u3000]".toRegex() /** - * Strip all the UTF-8 chars which are actually spaces + * Strip all the UTF-8 chars which are actually spaces. */ internal fun String.replaceSpaceChars(replacement: String = "") = replace(spaceChars, replacement) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/util/TemporaryFileCreator.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/util/TemporaryFileCreator.kt index 2790ffba36..dddd789212 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/util/TemporaryFileCreator.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/util/TemporaryFileCreator.kt @@ -29,6 +29,7 @@ internal class TemporaryFileCreator @Inject constructor( suspend fun create(): File { return withContext(Dispatchers.IO) { File.createTempFile(UUID.randomUUID().toString(), null, context.cacheDir) + .apply { mkdirs() } } } } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/util/UrlUtils.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/util/UrlUtils.kt index d13753b5ee..fbbec1a100 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/util/UrlUtils.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/util/UrlUtils.kt @@ -39,7 +39,7 @@ internal fun String.ensureProtocol(): String { } /** - * Ensure string has trailing / + * Ensure string ends with "/", if not empty. */ internal fun String.ensureTrailingSlash(): String { return when { diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/util/database/RealmMigrator.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/util/database/RealmMigrator.kt index f22f0810a1..8f3c89f2d4 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/util/database/RealmMigrator.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/util/database/RealmMigrator.kt @@ -21,7 +21,7 @@ import io.realm.RealmObjectSchema import timber.log.Timber internal abstract class RealmMigrator(private val realm: DynamicRealm, - private val targetSchemaVersion: Int) { + private val targetSchemaVersion: Int) { fun perform() { Timber.d("Migrate ${realm.configuration.realmFileName} to $targetSchemaVersion") doMigrate(realm) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/util/system/BuildVersionSdkIntProvider.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/util/system/BuildVersionSdkIntProvider.kt index b660796ad8..515656049a 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/util/system/BuildVersionSdkIntProvider.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/util/system/BuildVersionSdkIntProvider.kt @@ -18,7 +18,7 @@ package org.matrix.android.sdk.internal.util.system internal interface BuildVersionSdkIntProvider { /** - * Return the current version of the Android SDK + * Return the current version of the Android SDK. */ fun get(): Int } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/util/system/DefaultBuildVersionSdkIntProvider.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/util/system/DefaultBuildVersionSdkIntProvider.kt index 2d5e4b944a..806c6e9735 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/util/system/DefaultBuildVersionSdkIntProvider.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/util/system/DefaultBuildVersionSdkIntProvider.kt @@ -20,6 +20,6 @@ import android.os.Build import javax.inject.Inject internal class DefaultBuildVersionSdkIntProvider @Inject constructor() : - BuildVersionSdkIntProvider { + BuildVersionSdkIntProvider { override fun get() = Build.VERSION.SDK_INT } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/util/system/SystemModule.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/util/system/SystemModule.kt index 8a7b50175a..396d12f369 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/util/system/SystemModule.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/util/system/SystemModule.kt @@ -18,10 +18,15 @@ package org.matrix.android.sdk.internal.util.system import dagger.Binds import dagger.Module +import org.matrix.android.sdk.internal.util.time.Clock +import org.matrix.android.sdk.internal.util.time.DefaultClock @Module internal abstract class SystemModule { @Binds abstract fun bindBuildVersionSdkIntProvider(provider: DefaultBuildVersionSdkIntProvider): BuildVersionSdkIntProvider + + @Binds + abstract fun bindClock(clock: DefaultClock): Clock } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/util/time/Clock.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/util/time/Clock.kt new file mode 100644 index 0000000000..4fe9069b49 --- /dev/null +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/util/time/Clock.kt @@ -0,0 +1,36 @@ +/* + * Copyright (c) 2022 The Matrix.org Foundation C.I.C. + * + * 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 org.matrix.android.sdk.internal.util.time + +import javax.inject.Inject + +internal interface Clock { + fun epochMillis(): Long +} + +internal class DefaultClock @Inject constructor() : Clock { + + /** + * Provides a UTC epoch in milliseconds + * + * This value is not guaranteed to be correct with reality + * as a User can override the system time and date to any values. + */ + override fun epochMillis(): Long { + return System.currentTimeMillis() + } +} diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/wellknown/GetWellknownTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/wellknown/GetWellknownTask.kt index 82ff9a321f..0d4a5ac28f 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/wellknown/GetWellknownTask.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/wellknown/GetWellknownTask.kt @@ -39,16 +39,16 @@ import javax.net.ssl.HttpsURLConnection internal interface GetWellknownTask : Task { data class Params( /** - * domain, for instance "matrix.org" + * domain, for instance "matrix.org". * the URL will be https://{domain}/.well-known/matrix/client */ val domain: String, - val homeServerConnectionConfig: HomeServerConnectionConfig? + val homeServerConnectionConfig: HomeServerConnectionConfig ) } /** - * Inspired from AutoDiscovery class from legacy Matrix Android SDK + * Inspired from AutoDiscovery class from legacy Matrix Android SDK. */ internal class DefaultGetWellknownTask @Inject constructor( @Unauthenticated @@ -61,19 +61,15 @@ internal class DefaultGetWellknownTask @Inject constructor( return findClientConfig(params.domain, client) } - private fun buildClient(homeServerConnectionConfig: HomeServerConnectionConfig?): OkHttpClient { - return if (homeServerConnectionConfig != null) { - okHttpClient.get() - .newBuilder() - .addSocketFactory(homeServerConnectionConfig) - .build() - } else { - okHttpClient.get() - } + private fun buildClient(homeServerConnectionConfig: HomeServerConnectionConfig): OkHttpClient { + return okHttpClient.get() + .newBuilder() + .addSocketFactory(homeServerConnectionConfig) + .build() } /** - * Find client config + * Find client config. * * - Do the .well-known request * - validate homeserver url and identity server url if provide in .well-known result @@ -130,7 +126,7 @@ internal class DefaultGetWellknownTask @Inject constructor( } /** - * Return true if homeserver is valid, and (if applicable) if identity server is pingable + * Return true if homeserver is valid, and (if applicable) if identity server is pingable. */ private suspend fun validateHomeServer(homeServerBaseUrl: String, wellKnown: WellKnown, client: OkHttpClient): WellknownResult { val capabilitiesAPI = retrofitFactory.create(client, homeServerBaseUrl) @@ -168,7 +164,7 @@ internal class DefaultGetWellknownTask @Inject constructor( } /** - * Return true if identity server is pingable + * Return true if identity server is pingable. */ private suspend fun validateIdentityServer(identityServerBaseUrl: String, client: OkHttpClient): Boolean { val identityPingApi = retrofitFactory.create(client, identityServerBaseUrl) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/worker/AlwaysSuccessfulWorker.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/worker/AlwaysSuccessfulWorker.kt index 856d3debcf..c92b51fcb8 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/worker/AlwaysSuccessfulWorker.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/worker/AlwaysSuccessfulWorker.kt @@ -20,7 +20,7 @@ import androidx.work.Worker import androidx.work.WorkerParameters internal class AlwaysSuccessfulWorker(context: Context, params: WorkerParameters) : - Worker(context, params) { + Worker(context, params) { override fun doWork(): Result { return Result.success() diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/worker/Extensions.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/worker/Extensions.kt index d36e164713..e822c63b56 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/worker/Extensions.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/worker/Extensions.kt @@ -20,7 +20,7 @@ import androidx.work.OneTimeWorkRequest import org.matrix.android.sdk.internal.session.room.send.NoMerger /** - * If startChain parameter is true, the builder will have a inputMerger set to [NoMerger] + * If startChain parameter is true, the builder will have a inputMerger set to [NoMerger]. */ internal fun OneTimeWorkRequest.Builder.startChain(startChain: Boolean): OneTimeWorkRequest.Builder { if (startChain) { diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/worker/MatrixWorkerFactory.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/worker/MatrixWorkerFactory.kt index 0b451e9c34..95b3662c67 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/worker/MatrixWorkerFactory.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/worker/MatrixWorkerFactory.kt @@ -22,11 +22,7 @@ import androidx.work.ListenableWorker import androidx.work.WorkerFactory import androidx.work.WorkerParameters import org.matrix.android.sdk.internal.SessionManager -import org.matrix.android.sdk.internal.crypto.CancelGossipRequestWorker -import org.matrix.android.sdk.internal.crypto.SendGossipRequestWorker -import org.matrix.android.sdk.internal.crypto.SendGossipWorker import org.matrix.android.sdk.internal.crypto.crosssigning.UpdateTrustWorker -import org.matrix.android.sdk.internal.crypto.verification.SendVerificationMessageWorker import org.matrix.android.sdk.internal.di.MatrixScope import org.matrix.android.sdk.internal.session.content.UploadContentWorker import org.matrix.android.sdk.internal.session.group.GetGroupDataWorker @@ -56,8 +52,6 @@ internal class MatrixWorkerFactory @Inject constructor(private val sessionManage CheckFactoryWorker(appContext, workerParameters, true) AddPusherWorker::class.java.name -> AddPusherWorker(appContext, workerParameters, sessionManager) - CancelGossipRequestWorker::class.java.name -> - CancelGossipRequestWorker(appContext, workerParameters, sessionManager) GetGroupDataWorker::class.java.name -> GetGroupDataWorker(appContext, workerParameters, sessionManager) MultipleEventSendingDispatcherWorker::class.java.name -> @@ -66,12 +60,6 @@ internal class MatrixWorkerFactory @Inject constructor(private val sessionManage RedactEventWorker(appContext, workerParameters, sessionManager) SendEventWorker::class.java.name -> SendEventWorker(appContext, workerParameters, sessionManager) - SendGossipRequestWorker::class.java.name -> - SendGossipRequestWorker(appContext, workerParameters, sessionManager) - SendGossipWorker::class.java.name -> - SendGossipWorker(appContext, workerParameters, sessionManager) - SendVerificationMessageWorker::class.java.name -> - SendVerificationMessageWorker(appContext, workerParameters, sessionManager) SyncWorker::class.java.name -> SyncWorker(appContext, workerParameters, sessionManager) UpdateTrustWorker::class.java.name -> @@ -93,12 +81,14 @@ internal class MatrixWorkerFactory @Inject constructor(private val sessionManage class CheckFactoryWorker(context: Context, workerParameters: WorkerParameters, private val isCreatedByMatrixWorkerFactory: Boolean) : - CoroutineWorker(context, workerParameters) { + CoroutineWorker(context, workerParameters) { // Called by WorkManager if there is no MatrixWorkerFactory - constructor(context: Context, workerParameters: WorkerParameters) : this(context, + constructor(context: Context, workerParameters: WorkerParameters) : this( + context, workerParameters, - isCreatedByMatrixWorkerFactory = false) + isCreatedByMatrixWorkerFactory = false + ) override suspend fun doWork(): Result { return if (!isCreatedByMatrixWorkerFactory) { diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/worker/SessionSafeCoroutineWorker.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/worker/SessionSafeCoroutineWorker.kt index 334c4580e9..030f51428b 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/worker/SessionSafeCoroutineWorker.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/worker/SessionSafeCoroutineWorker.kt @@ -69,7 +69,7 @@ internal abstract class SessionSafeCoroutineWorker( abstract fun injectWith(injector: SessionComponent) /** - * Should only return Result.Success for workers added to a unique queue + * Should only return Result.Success for workers added to a unique queue. */ abstract suspend fun doSafeWork(params: PARAM): Result diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/worker/SessionWorkerParams.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/worker/SessionWorkerParams.kt index de36b85660..36d03f7fcf 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/worker/SessionWorkerParams.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/worker/SessionWorkerParams.kt @@ -18,14 +18,14 @@ package org.matrix.android.sdk.internal.worker /** * Note about the Worker usage: - * The workers we chain, or when using the append strategy, should never return Result.Failure(), else the chain will be broken forever + * The workers we chain, or when using the append strategy, should never return Result.Failure(), else the chain will be broken forever. */ internal interface SessionWorkerParams { val sessionId: String /** * Null when no error occurs. When chaining Workers, first step is to check that there is no lastFailureMessage from the previous workers - * If it is the case, the worker should just transmit the error and shouldn't do anything else + * If it is the case, the worker should just transmit the error and shouldn't do anything else. */ val lastFailureMessage: String? } diff --git a/matrix-sdk-android/src/release/java/org/matrix/android/sdk/internal/network/interceptors/CurlLoggingInterceptor.kt b/matrix-sdk-android/src/release/java/org/matrix/android/sdk/internal/network/interceptors/CurlLoggingInterceptor.kt index 47f6869479..0b7bac0d6f 100644 --- a/matrix-sdk-android/src/release/java/org/matrix/android/sdk/internal/network/interceptors/CurlLoggingInterceptor.kt +++ b/matrix-sdk-android/src/release/java/org/matrix/android/sdk/internal/network/interceptors/CurlLoggingInterceptor.kt @@ -27,7 +27,7 @@ import javax.inject.Inject */ @MatrixScope internal class CurlLoggingInterceptor @Inject constructor() : - Interceptor { + Interceptor { @Throws(IOException::class) override fun intercept(chain: Interceptor.Chain): Response { diff --git a/matrix-sdk-android/src/test/java/org/matrix/android/sdk/api/auth/data/VersionsKtTest.kt b/matrix-sdk-android/src/test/java/org/matrix/android/sdk/api/auth/data/VersionsKtTest.kt index 088e160950..e1438f4741 100644 --- a/matrix-sdk-android/src/test/java/org/matrix/android/sdk/api/auth/data/VersionsKtTest.kt +++ b/matrix-sdk-android/src/test/java/org/matrix/android/sdk/api/auth/data/VersionsKtTest.kt @@ -62,12 +62,12 @@ class VersionsKtTest { Versions(supportedVersions = listOf("r0.6.0")).doesServerSupportThreads() shouldBe false Versions(supportedVersions = listOf("r0.9.1")).doesServerSupportThreads() shouldBe false Versions(supportedVersions = listOf("v1.2.0")).doesServerSupportThreads() shouldBe false - Versions(supportedVersions = listOf("v1.3.0")).doesServerSupportThreads() shouldBe true - Versions(supportedVersions = listOf("v1.3.1")).doesServerSupportThreads() shouldBe true - Versions(supportedVersions = listOf("v1.5.1")).doesServerSupportThreads() shouldBe true + Versions(supportedVersions = listOf("v1.3.0")).doesServerSupportThreads() shouldBe false + Versions(supportedVersions = listOf("v1.3.1")).doesServerSupportThreads() shouldBe false + Versions(supportedVersions = listOf("v1.5.1")).doesServerSupportThreads() shouldBe false Versions(supportedVersions = listOf("r0.6.0"), unstableFeatures = mapOf("org.matrix.msc3440.stable" to true)).doesServerSupportThreads() shouldBe true Versions(supportedVersions = listOf("v1.2.1"), unstableFeatures = mapOf("org.matrix.msc3440.stable" to true)).doesServerSupportThreads() shouldBe true Versions(supportedVersions = listOf("r0.6.0"), unstableFeatures = mapOf("org.matrix.msc3440.stable" to false)).doesServerSupportThreads() shouldBe false - Versions(supportedVersions = listOf("v1.4.0"), unstableFeatures = mapOf("org.matrix.msc3440.stable" to false)).doesServerSupportThreads() shouldBe true + Versions(supportedVersions = listOf("v1.4.0"), unstableFeatures = mapOf("org.matrix.msc3440.stable" to false)).doesServerSupportThreads() shouldBe false } } diff --git a/matrix-sdk-android/src/test/java/org/matrix/android/sdk/api/pushrules/PushRuleActionsTest.kt b/matrix-sdk-android/src/test/java/org/matrix/android/sdk/api/session/pushrules/PushRuleActionsTest.kt similarity index 95% rename from matrix-sdk-android/src/test/java/org/matrix/android/sdk/api/pushrules/PushRuleActionsTest.kt rename to matrix-sdk-android/src/test/java/org/matrix/android/sdk/api/session/pushrules/PushRuleActionsTest.kt index 9bfdea5414..1879e8195c 100644 --- a/matrix-sdk-android/src/test/java/org/matrix/android/sdk/api/pushrules/PushRuleActionsTest.kt +++ b/matrix-sdk-android/src/test/java/org/matrix/android/sdk/api/session/pushrules/PushRuleActionsTest.kt @@ -14,14 +14,14 @@ * limitations under the License. */ -package org.matrix.android.sdk.api.pushrules +package org.matrix.android.sdk.api.session.pushrules import org.junit.Assert.assertEquals import org.junit.Assert.assertNotNull import org.junit.Assert.assertTrue import org.junit.Test import org.matrix.android.sdk.MatrixTest -import org.matrix.android.sdk.api.pushrules.rest.PushRule +import org.matrix.android.sdk.api.session.pushrules.rest.PushRule import org.matrix.android.sdk.internal.di.MoshiProvider class PushRuleActionsTest : MatrixTest { diff --git a/matrix-sdk-android/src/test/java/org/matrix/android/sdk/api/pushrules/PushRulesConditionTest.kt b/matrix-sdk-android/src/test/java/org/matrix/android/sdk/api/session/pushrules/PushRulesConditionTest.kt similarity index 91% rename from matrix-sdk-android/src/test/java/org/matrix/android/sdk/api/pushrules/PushRulesConditionTest.kt rename to matrix-sdk-android/src/test/java/org/matrix/android/sdk/api/session/pushrules/PushRulesConditionTest.kt index c0b869a90e..c4a3404e80 100644 --- a/matrix-sdk-android/src/test/java/org/matrix/android/sdk/api/pushrules/PushRulesConditionTest.kt +++ b/matrix-sdk-android/src/test/java/org/matrix/android/sdk/api/session/pushrules/PushRulesConditionTest.kt @@ -14,7 +14,7 @@ * limitations under the License. */ -package org.matrix.android.sdk.api.pushrules +package org.matrix.android.sdk.api.session.pushrules import io.mockk.every import io.mockk.mockk @@ -26,6 +26,7 @@ import org.matrix.android.sdk.MatrixTest import org.matrix.android.sdk.api.session.events.model.Event import org.matrix.android.sdk.api.session.events.model.toContent import org.matrix.android.sdk.api.session.room.Room +import org.matrix.android.sdk.api.session.room.members.MembershipService import org.matrix.android.sdk.api.session.room.model.Membership import org.matrix.android.sdk.api.session.room.model.RoomMemberContent import org.matrix.android.sdk.api.session.room.model.message.MessageTextContent @@ -42,7 +43,8 @@ class PushRulesConditionTest : MatrixTest { type = "m.room.message", eventId = "mx0", content = MessageTextContent("m.text", text).toContent(), - originServerTs = 0) + originServerTs = 0 + ) } @Test @@ -61,7 +63,8 @@ class PushRulesConditionTest : MatrixTest { eventId = "mx0", stateKey = "@foo:matrix.org", content = rm.toContent(), - originServerTs = 0) + originServerTs = 0 + ) assert(condition.isSatisfied(simpleTextEvent)) assert(!condition.isSatisfied(simpleRoomMemberEvent)) @@ -130,7 +133,8 @@ class PushRulesConditionTest : MatrixTest { eventId = "mx0", content = MessageTextContent("m.notice", "A").toContent(), originServerTs = 0, - roomId = "2joined").also { + roomId = "2joined" + ).also { assertTrue("Notice", conditionEqual.isSatisfied(it)) } } @@ -148,14 +152,22 @@ class PushRulesConditionTest : MatrixTest { val room2JoinedId = "2joined" val room3JoinedId = "3joined" - val roomStub2Joined = mockk { + val roomMembershipService2 = mockk { every { getNumberOfJoinedMembers() } returns 2 } - val roomStub3Joined = mockk { + val roomMembershipService3 = mockk { every { getNumberOfJoinedMembers() } returns 3 } + val roomStub2Joined = mockk { + every { membershipService() } returns roomMembershipService2 + } + + val roomStub3Joined = mockk { + every { membershipService() } returns roomMembershipService3 + } + val roomGetterStub = mockk { every { getRoom(room2JoinedId) } returns roomStub2Joined every { getRoom(room3JoinedId) } returns roomStub3Joined @@ -166,7 +178,8 @@ class PushRulesConditionTest : MatrixTest { eventId = "mx0", content = MessageTextContent("m.text", "A").toContent(), originServerTs = 0, - roomId = room2JoinedId).also { + roomId = room2JoinedId + ).also { assertFalse("This room does not have 3 members", conditionEqual3.isSatisfied(it, roomGetterStub)) assertFalse("This room does not have 3 members", conditionEqual3Bis.isSatisfied(it, roomGetterStub)) assertTrue("This room has less than 3 members", conditionLessThan3.isSatisfied(it, roomGetterStub)) @@ -177,7 +190,8 @@ class PushRulesConditionTest : MatrixTest { eventId = "mx0", content = MessageTextContent("m.text", "A").toContent(), originServerTs = 0, - roomId = room3JoinedId).also { + roomId = room3JoinedId + ).also { assertTrue("This room has 3 members", conditionEqual3.isSatisfied(it, roomGetterStub)) assertTrue("This room has 3 members", conditionEqual3Bis.isSatisfied(it, roomGetterStub)) assertFalse("This room has more than 3 members", conditionLessThan3.isSatisfied(it, roomGetterStub)) @@ -197,7 +211,8 @@ class PushRulesConditionTest : MatrixTest { eventId = "mx0", content = MessageTextContent("m.text", "How was the cake benoit?").toContent(), originServerTs = 0, - roomId = "2joined") + roomId = "2joined" + ) condition.isSatisfied(event, "how") shouldBe true condition.isSatisfied(event, "How") shouldBe true diff --git a/matrix-sdk-android/src/test/java/org/matrix/android/sdk/internal/crypto/keysbackup/util/Base58Test.kt b/matrix-sdk-android/src/test/java/org/matrix/android/sdk/internal/crypto/keysbackup/util/Base58Test.kt index a93883a344..e6fe8fbb00 100644 --- a/matrix-sdk-android/src/test/java/org/matrix/android/sdk/internal/crypto/keysbackup/util/Base58Test.kt +++ b/matrix-sdk-android/src/test/java/org/matrix/android/sdk/internal/crypto/keysbackup/util/Base58Test.kt @@ -41,13 +41,17 @@ class Base58Test : MatrixTest { @Test fun encode_curve25519() { // Encode a 32 bytes key - assertEquals("4F85ZySpwyY6FuH7mQYyyr5b8nV9zFRBLj92AJa37sMr", - base58encode(("0123456789" + "0123456789" + "0123456789" + "01").toByteArray())) + assertEquals( + "4F85ZySpwyY6FuH7mQYyyr5b8nV9zFRBLj92AJa37sMr", + base58encode(("0123456789" + "0123456789" + "0123456789" + "01").toByteArray()) + ) } @Test fun decode_curve25519() { - assertArrayEquals(("0123456789" + "0123456789" + "0123456789" + "01").toByteArray(), - base58decode("4F85ZySpwyY6FuH7mQYyyr5b8nV9zFRBLj92AJa37sMr")) + assertArrayEquals( + ("0123456789" + "0123456789" + "0123456789" + "01").toByteArray(), + base58decode("4F85ZySpwyY6FuH7mQYyyr5b8nV9zFRBLj92AJa37sMr") + ) } } diff --git a/matrix-sdk-android/src/test/java/org/matrix/android/sdk/internal/crypto/keysbackup/util/RecoveryKeyTest.kt b/matrix-sdk-android/src/test/java/org/matrix/android/sdk/internal/crypto/keysbackup/util/RecoveryKeyTest.kt index d4c9da2986..4146e6b3b7 100644 --- a/matrix-sdk-android/src/test/java/org/matrix/android/sdk/internal/crypto/keysbackup/util/RecoveryKeyTest.kt +++ b/matrix-sdk-android/src/test/java/org/matrix/android/sdk/internal/crypto/keysbackup/util/RecoveryKeyTest.kt @@ -32,7 +32,8 @@ class RecoveryKeyTest : MatrixTest { 0x77.toByte(), 0x07.toByte(), 0x6D.toByte(), 0x0A.toByte(), 0x73.toByte(), 0x18.toByte(), 0xA5.toByte(), 0x7D.toByte(), 0x3C.toByte(), 0x16.toByte(), 0xC1.toByte(), 0x72.toByte(), 0x51.toByte(), 0xB2.toByte(), 0x66.toByte(), 0x45.toByte(), 0xDF.toByte(), 0x4C.toByte(), 0x2F.toByte(), 0x87.toByte(), 0xEB.toByte(), 0xC0.toByte(), 0x99.toByte(), 0x2A.toByte(), - 0xB1.toByte(), 0x77.toByte(), 0xFB.toByte(), 0xA5.toByte(), 0x1D.toByte(), 0xB9.toByte(), 0x2C.toByte(), 0x2A.toByte()) + 0xB1.toByte(), 0x77.toByte(), 0xFB.toByte(), 0xA5.toByte(), 0x1D.toByte(), 0xB9.toByte(), 0x2C.toByte(), 0x2A.toByte() + ) @Test fun isValidRecoveryKey_valid_true() { diff --git a/matrix-sdk-android/src/test/java/org/matrix/android/sdk/internal/crypto/verification/qrcode/BinaryStringTest.kt b/matrix-sdk-android/src/test/java/org/matrix/android/sdk/internal/crypto/verification/qrcode/BinaryStringTest.kt index 5a82052d1a..b7951fcc6e 100644 --- a/matrix-sdk-android/src/test/java/org/matrix/android/sdk/internal/crypto/verification/qrcode/BinaryStringTest.kt +++ b/matrix-sdk-android/src/test/java/org/matrix/android/sdk/internal/crypto/verification/qrcode/BinaryStringTest.kt @@ -26,7 +26,7 @@ import org.matrix.android.sdk.MatrixTest class BinaryStringTest : MatrixTest { /** - * I want to put bytes to a String, and vice versa + * I want to put bytes to a String, and vice versa. */ @Test fun testNominalCase() { diff --git a/matrix-sdk-android/src/test/java/org/matrix/android/sdk/internal/session/pushers/DefaultAddPusherTaskTest.kt b/matrix-sdk-android/src/test/java/org/matrix/android/sdk/internal/session/pushers/DefaultAddPusherTaskTest.kt index 31fd86fe65..32b1d44fb9 100644 --- a/matrix-sdk-android/src/test/java/org/matrix/android/sdk/internal/session/pushers/DefaultAddPusherTaskTest.kt +++ b/matrix-sdk-android/src/test/java/org/matrix/android/sdk/internal/session/pushers/DefaultAddPusherTaskTest.kt @@ -81,7 +81,7 @@ class DefaultAddPusherTaskTest { } @Test - fun `given a persisted push entity and SetPush API fails when adding Pusher then mutates persisted result with Failed registration state and rethrows error`() { + fun `given a persisted push entity and SetPush API fails when adding Pusher then mutates persisted result with Failed registration state and rethrows`() { val realmResult = PusherEntity() monarchy.givenWhereReturns(result = realmResult) pushersAPI.givenSetPusherErrors(SocketException()) diff --git a/matrix-sdk-android/src/test/java/org/matrix/android/sdk/internal/task/CoroutineSequencersTest.kt b/matrix-sdk-android/src/test/java/org/matrix/android/sdk/internal/task/CoroutineSequencersTest.kt index 149b964fd2..0cfd9d97a8 100644 --- a/matrix-sdk-android/src/test/java/org/matrix/android/sdk/internal/task/CoroutineSequencersTest.kt +++ b/matrix-sdk-android/src/test/java/org/matrix/android/sdk/internal/task/CoroutineSequencersTest.kt @@ -16,6 +16,8 @@ package org.matrix.android.sdk.internal.task +import kotlinx.coroutines.DelicateCoroutinesApi +import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.GlobalScope import kotlinx.coroutines.asCoroutineDispatcher import kotlinx.coroutines.delay @@ -31,8 +33,8 @@ class CoroutineSequencersTest : MatrixTest { private val dispatcher = Executors.newSingleThreadExecutor().asCoroutineDispatcher() + @OptIn(DelicateCoroutinesApi::class, ExperimentalCoroutinesApi::class) @Test - @Suppress("EXPERIMENTAL_API_USAGE") fun sequencer_should_run_sequential() { val sequencer = SemaphoreCoroutineSequencer() val results = ArrayList() @@ -60,8 +62,8 @@ class CoroutineSequencersTest : MatrixTest { assertEquals(results[2], "#3") } + @OptIn(DelicateCoroutinesApi::class, ExperimentalCoroutinesApi::class) @Test - @Suppress("EXPERIMENTAL_API_USAGE") fun sequencer_should_run_parallel() { val sequencer1 = SemaphoreCoroutineSequencer() val sequencer2 = SemaphoreCoroutineSequencer() @@ -87,8 +89,8 @@ class CoroutineSequencersTest : MatrixTest { assertEquals(3, results.size) } + @OptIn(DelicateCoroutinesApi::class, ExperimentalCoroutinesApi::class) @Test - @Suppress("EXPERIMENTAL_API_USAGE") fun sequencer_should_jump_to_next_when_current_job_canceled() { val sequencer = SemaphoreCoroutineSequencer() val results = ArrayList() diff --git a/tools/check/forbidden_strings_in_code.txt b/tools/check/forbidden_strings_in_code.txt index 21ab0bab77..7362ff2d10 100755 --- a/tools/check/forbidden_strings_in_code.txt +++ b/tools/check/forbidden_strings_in_code.txt @@ -60,7 +60,7 @@ private short final short ### Line length is limited to 160 chars. Please split long lines -[^─]{161} +#[^─]{161} ### "DO NOT COMMIT" has been committed DO NOT COMMIT @@ -173,4 +173,7 @@ PreferenceManager\.getDefaultSharedPreferences==2 # findViewById ### Do not use `template_` string. Please remove the prefix `template_` to use the generated resource instead. -R\.string\.template_ \ No newline at end of file +R\.string\.template_ + +### Use the Clock interface, or use `measureTimeMillis` +System\.currentTimeMillis\(\)===2 diff --git a/tools/detekt/detekt.yml b/tools/detekt/detekt.yml new file mode 100644 index 0000000000..322f29e5b7 --- /dev/null +++ b/tools/detekt/detekt.yml @@ -0,0 +1,97 @@ +# Default rules: https://github.com/detekt/detekt/blob/main/detekt-core/src/main/resources/default-detekt-config.yml + +style: + MaxLineLength: + # Default is 120 + maxLineLength: 160 + MagicNumber: + active: false + ReturnCount: + active: false + UnnecessaryAbstractClass: + active: false + FunctionOnlyReturningConstant: + active: false + UnusedPrivateMember: + # TODO Enable it + active: false + ThrowsCount: + active: false + LoopWithTooManyJumpStatements: + active: false + SerialVersionUIDInSerializableClass: + active: false + ProtectedMemberInFinalClass: + active: false + +empty-blocks: + EmptyFunctionBlock: + active: false + EmptySecondaryConstructor: + active: false + +potential-bugs: + ImplicitDefaultLocale: + active: false + +exceptions: + TooGenericExceptionCaught: + active: false + SwallowedException: + active: false + ThrowingExceptionsWithoutMessageOrCause: + active: false + TooGenericExceptionThrown: + active: false + +complexity: + TooManyFunctions: + active: false + LongMethod: + active: false + LongParameterList: + active: false + ComplexMethod: + active: false + NestedBlockDepth: + active: false + ComplexCondition: + active: false + LargeClass: + active: false + +naming: + VariableNaming: + # TODO Enable it + active: false + TopLevelPropertyNaming: + # TODO Enable it + active: false + +performance: + SpreadOperator: + active: false + +# Note: all rules for `comments` are disabled by default, but I put them here to be aware of their existence +comments: + AbsentOrWrongFileLicense: + active: false + licenseTemplateFile: 'license.template' + licenseTemplateIsRegex: false + CommentOverPrivateFunction: + active: false + CommentOverPrivateProperty: + active: false + DeprecatedBlockTag: + active: true + EndOfSentenceFormat: + active: true + OutdatedDocumentation: + # TODO Enable it + active: false + UndocumentedPublicClass: + active: false + UndocumentedPublicFunction: + active: false + UndocumentedPublicProperty: + active: false diff --git a/tools/hs_diag.py b/tools/hs_diag.py index 50f117bc8e..8651321fa3 100755 --- a/tools/hs_diag.py +++ b/tools/hs_diag.py @@ -58,6 +58,9 @@ items = [ # Need token , ["Capability", baseUrl + "_matrix/client/r0/capabilities", True] # Need token , ["Media config", baseUrl + "_matrix/media/r0/config", True] # Need token , ["Turn", baseUrl + "_matrix/client/r0/voip/turnServer", True] + + # Only for Synapse + , ["Synapse version", baseUrl + "_synapse/admin/v1/server_version", True] ] for item in items: diff --git a/tools/templates/ElementFeature/root/src/app_package/Activity.kt.ftl b/tools/templates/ElementFeature/root/src/app_package/Activity.kt.ftl index a5c097065e..de63de6c06 100644 --- a/tools/templates/ElementFeature/root/src/app_package/Activity.kt.ftl +++ b/tools/templates/ElementFeature/root/src/app_package/Activity.kt.ftl @@ -8,7 +8,7 @@ import im.vector.app.core.extensions.addFragment import im.vector.app.core.platform.ToolbarConfigurable import im.vector.app.core.platform.VectorBaseActivity -//TODO: add this activity to manifest +//TODO add this activity to manifest class ${activityClass} : VectorBaseActivity(), ToolbarConfigurable { companion object { diff --git a/tools/templates/ElementFeature/root/src/app_package/Fragment.kt.ftl b/tools/templates/ElementFeature/root/src/app_package/Fragment.kt.ftl index 38f8132d24..0f01b347c0 100644 --- a/tools/templates/ElementFeature/root/src/app_package/Fragment.kt.ftl +++ b/tools/templates/ElementFeature/root/src/app_package/Fragment.kt.ftl @@ -18,7 +18,7 @@ import javax.inject.Inject data class ${fragmentArgsClass}() : Parcelable -//TODO: add this fragment into FragmentModule +//TODO add this fragment into FragmentModule class ${fragmentClass} @Inject constructor( private val viewModelFactory: ${viewModelClass}.Factory ) : VectorBaseFragment(), ${viewModelClass}.Factory by viewModelFactory { diff --git a/vector-config/src/main/res/values/config-settings.xml b/vector-config/src/main/res/values/config-settings.xml index b6d1c6676c..b2cd21c3de 100755 --- a/vector-config/src/main/res/values/config-settings.xml +++ b/vector-config/src/main/res/values/config-settings.xml @@ -13,7 +13,6 @@ true true true - true true true true @@ -33,13 +32,14 @@ - - + true + false - + true + false diff --git a/vector/build.gradle b/vector/build.gradle index 6d318f82b9..c91b7b50b7 100644 --- a/vector/build.gradle +++ b/vector/build.gradle @@ -7,18 +7,31 @@ apply plugin: 'kotlin-parcelize' apply plugin: 'kotlin-kapt' apply plugin: 'com.likethesalad.stem' apply plugin: 'dagger.hilt.android.plugin' +apply plugin: 'kotlinx-knit' kapt { correctErrorTypes = true } +knit { + files = fileTree(project.rootDir) { + include '**/*.md' + include '**/*.kt' + include '**/*.kts' + exclude '**/build/**' + exclude '**/.gradle/**' + exclude '**/towncrier/template.md' + exclude '**/CHANGES.md' + } +} + // Note: 2 digits max for each value ext.versionMajor = 1 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 = 12 +ext.versionPatch = 18 static def getGitTimestamp() { def cmd = 'git show -s --format=%ct' @@ -297,14 +310,14 @@ android { kotlinOptions { jvmTarget = "11" freeCompilerArgs += [ - "-Xopt-in=kotlin.RequiresOptIn", + "-opt-in=kotlin.RequiresOptIn", // Fixes false positive "This is an internal Mavericks API. It is not intended for external use." // of MvRx `by viewModel()` calls. Maybe due to the inlining of code... This is a temporary fix... - "-Xopt-in=com.airbnb.mvrx.InternalMavericksApi", + "-opt-in=com.airbnb.mvrx.InternalMavericksApi", // Opt in for kotlinx.coroutines.FlowPreview too - "-Xopt-in=kotlinx.coroutines.FlowPreview", + "-opt-in=kotlinx.coroutines.FlowPreview", // Opt in for kotlinx.coroutines.ExperimentalCoroutinesApi too - "-Xopt-in=kotlinx.coroutines.ExperimentalCoroutinesApi", + "-opt-in=kotlinx.coroutines.ExperimentalCoroutinesApi", ] } @@ -343,11 +356,11 @@ dependencies { implementation libs.androidx.constraintLayout implementation "androidx.sharetarget:sharetarget:1.1.0" implementation libs.androidx.core - implementation "androidx.media:media:1.4.3" + implementation "androidx.media:media:1.6.0" implementation "androidx.transition:transition:1.4.1" implementation "org.threeten:threetenbp:1.4.0:no-tzdb" - implementation "com.gabrielittner.threetenbp:lazythreetenbp:0.9.0" + implementation "com.gabrielittner.threetenbp:lazythreetenbp:0.10.0" implementation libs.squareup.moshi kapt libs.squareup.moshiKotlin @@ -368,7 +381,7 @@ dependencies { implementation 'com.facebook.stetho:stetho:1.6.0' // Phone number https://github.com/google/libphonenumber - implementation 'com.googlecode.libphonenumber:libphonenumber:8.12.46' + implementation 'com.googlecode.libphonenumber:libphonenumber:8.12.48' // FlowBinding implementation libs.github.flowBinding @@ -412,10 +425,10 @@ dependencies { implementation 'androidx.browser:browser:1.4.0' // Passphrase strength helper - implementation 'com.nulab-inc:zxcvbn:1.5.2' + implementation 'com.nulab-inc:zxcvbn:1.7.0' - // To convert voice message on old platforms - implementation 'com.arthenica:ffmpeg-kit-audio:4.5.LTS' + // To convert voice message on old platforms. Always keep the LTS suffix! + implementation 'com.arthenica:ffmpeg-kit-audio:4.5.1.LTS' // Alerter implementation 'com.github.tapadoo:alerter:7.2.4' @@ -462,7 +475,7 @@ dependencies { // OSS License, gplay flavor only gplayImplementation 'com.google.android.gms:play-services-oss-licenses:17.0.0' - implementation "androidx.emoji2:emoji2:1.0.1" + implementation "androidx.emoji2:emoji2:1.1.0" // WebRTC // org.webrtc:google-webrtc is for development purposes only diff --git a/vector/lint.xml b/vector/lint.xml index e219ac1eed..d9ca761e7a 100644 --- a/vector/lint.xml +++ b/vector/lint.xml @@ -1,5 +1,9 @@ + + + + diff --git a/vector/src/androidTest/java/im/vector/app/EspressoExt.kt b/vector/src/androidTest/java/im/vector/app/EspressoExt.kt index 5fb404efe3..efb28cdff5 100644 --- a/vector/src/androidTest/java/im/vector/app/EspressoExt.kt +++ b/vector/src/androidTest/java/im/vector/app/EspressoExt.kt @@ -43,6 +43,7 @@ import com.google.android.material.bottomsheet.BottomSheetBehavior import com.google.android.material.bottomsheet.BottomSheetDialog import com.google.android.material.bottomsheet.BottomSheetDialogFragment import im.vector.app.core.platform.VectorBaseBottomSheetDialogFragment +import im.vector.app.core.time.DefaultClock import im.vector.app.espresso.tools.waitUntilViewVisible import org.hamcrest.Matcher import org.hamcrest.Matchers @@ -52,6 +53,7 @@ import org.matrix.android.sdk.api.session.crypto.crosssigning.PrivateKeysInfo import org.matrix.android.sdk.api.session.sync.SyncState import org.matrix.android.sdk.api.util.Optional import java.util.concurrent.TimeoutException +import kotlin.random.Random object EspressoHelper { fun getCurrentActivity(): Activity? { @@ -89,6 +91,8 @@ fun getString(@StringRes id: Int): String { fun waitForView(viewMatcher: Matcher, timeout: Long = 10_000, waitForDisplayed: Boolean = true): ViewAction { return object : ViewAction { + private val clock = DefaultClock() + override fun getConstraints(): Matcher { return Matchers.any(View::class.java) } @@ -102,14 +106,14 @@ fun waitForView(viewMatcher: Matcher, timeout: Long = 10_000, waitForDispl override fun perform(uiController: UiController, view: View) { println("*** waitForView 1 $view") uiController.loopMainThreadUntilIdle() - val startTime = System.currentTimeMillis() + val startTime = clock.epochMillis() val endTime = startTime + timeout val visibleMatcher = isDisplayed() uiController.loopMainThreadForAtLeast(100) do { - println("*** waitForView loop $view end:$endTime current:${System.currentTimeMillis()}") + println("*** waitForView loop $view end:$endTime current:${clock.epochMillis()}") val viewVisible = TreeIterables.breadthFirstViewTraversal(view) .any { viewMatcher.matches(it) && visibleMatcher.matches(it) } @@ -118,7 +122,7 @@ fun waitForView(viewMatcher: Matcher, timeout: Long = 10_000, waitForDispl println("*** waitForView loop loopMainThreadForAtLeast...") uiController.loopMainThreadForAtLeast(50) println("*** waitForView loop ...loopMainThreadForAtLeast") - } while (System.currentTimeMillis() < endTime) + } while (clock.epochMillis() < endTime) println("*** waitForView timeout $view") // Timeout happens. @@ -168,9 +172,9 @@ fun activityIdlingResource(activityClass: Class<*>): IdlingResource { val res = object : IdlingResource, ActivityLifecycleCallback { private var callback: IdlingResource.ResourceCallback? = null private var resumedActivity: Activity? = null - private val uniqTS = System.currentTimeMillis() + private val uniqueSuffix = Random.nextLong() - override fun getName() = "activityIdlingResource_${activityClass.name}_$uniqTS" + override fun getName() = "activityIdlingResource_${activityClass.name}_$uniqueSuffix" override fun isIdleNow(): Boolean { val activity = resumedActivity ?: ActivityLifecycleMonitorRegistry.getInstance().getActivitiesInStage(Stage.RESUMED).firstOrNull { @@ -198,7 +202,7 @@ fun activityIdlingResource(activityClass: Class<*>): IdlingResource { println("*** [$name] onActivityLifecycleChanged callback: $callback") callback?.onTransitionToIdle() } - else -> { + else -> { // do nothing, we're blocking until the activity resumes } } diff --git a/vector/src/androidTest/java/im/vector/app/RegistrationTest.kt b/vector/src/androidTest/java/im/vector/app/RegistrationTest.kt index fcec2a9fb5..344a2ecfb1 100644 --- a/vector/src/androidTest/java/im/vector/app/RegistrationTest.kt +++ b/vector/src/androidTest/java/im/vector/app/RegistrationTest.kt @@ -34,6 +34,7 @@ import org.hamcrest.CoreMatchers.not import org.junit.Rule import org.junit.Test import org.junit.runner.RunWith +import kotlin.random.Random @RunWith(AndroidJUnit4::class) @LargeTest @@ -44,7 +45,7 @@ class RegistrationTest { @Test fun simpleRegister() { - val userId: String = "UiAutoTest_${System.currentTimeMillis()}" + val userId: String = "UiAutoTest_${Random.nextLong()}" val password: String = "password" val homeServerUrl: String = "http://10.0.2.2:8080" diff --git a/vector/src/androidTest/java/im/vector/app/SecurityBootstrapTest.kt b/vector/src/androidTest/java/im/vector/app/SecurityBootstrapTest.kt index 69fe63fb7b..21fd226317 100644 --- a/vector/src/androidTest/java/im/vector/app/SecurityBootstrapTest.kt +++ b/vector/src/androidTest/java/im/vector/app/SecurityBootstrapTest.kt @@ -48,6 +48,7 @@ import org.junit.Rule import org.junit.Test import org.junit.runner.RunWith import org.matrix.android.sdk.api.session.Session +import kotlin.random.Random @RunWith(AndroidJUnit4::class) @LargeTest @@ -61,7 +62,7 @@ class SecurityBootstrapTest : VerificationTestBase() { @Before fun createSessionWithCrossSigning() { val matrix = getMatrixInstance() - val userName = "foobar_${System.currentTimeMillis()}" + val userName = "foobar_${Random.nextLong()}" existingSession = createAccountAndSync(matrix, userName, password, true) stubAllExternalIntents() } @@ -166,7 +167,7 @@ class SecurityBootstrapTest : VerificationTestBase() { assert(uiSession.cryptoService().crossSigningService().allPrivateKeysKnown()) assert(uiSession.cryptoService().keysBackupService().isEnabled) assert(uiSession.cryptoService().keysBackupService().currentBackupVersion != null) - assert(uiSession.sharedSecretStorageService.isRecoverySetup()) - assert(uiSession.sharedSecretStorageService.isMegolmKeyInBackup()) + assert(uiSession.sharedSecretStorageService().isRecoverySetup()) + assert(uiSession.sharedSecretStorageService().isMegolmKeyInBackup()) } } diff --git a/vector/src/androidTest/java/im/vector/app/VerificationTestBase.kt b/vector/src/androidTest/java/im/vector/app/VerificationTestBase.kt index 47e1e43be3..6ac3226674 100644 --- a/vector/src/androidTest/java/im/vector/app/VerificationTestBase.kt +++ b/vector/src/androidTest/java/im/vector/app/VerificationTestBase.kt @@ -19,6 +19,7 @@ package im.vector.app import android.net.Uri import androidx.lifecycle.Observer import im.vector.app.ui.robot.OnboardingRobot +import kotlinx.coroutines.DelicateCoroutinesApi import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.GlobalScope import kotlinx.coroutines.launch @@ -107,7 +108,7 @@ abstract class VerificationTestBase { return result!! } - @Suppress("EXPERIMENTAL_API_USAGE") + @OptIn(DelicateCoroutinesApi::class) private fun syncSession(session: Session) { val lock = CountDownLatch(1) diff --git a/vector/src/androidTest/java/im/vector/app/VerifySessionInteractiveTest.kt b/vector/src/androidTest/java/im/vector/app/VerifySessionInteractiveTest.kt index c82b543a08..c970d0049f 100644 --- a/vector/src/androidTest/java/im/vector/app/VerifySessionInteractiveTest.kt +++ b/vector/src/androidTest/java/im/vector/app/VerifySessionInteractiveTest.kt @@ -53,6 +53,7 @@ import org.matrix.android.sdk.api.session.crypto.verification.VerificationTransa import org.matrix.android.sdk.api.session.crypto.verification.VerificationTxState import kotlin.coroutines.Continuation import kotlin.coroutines.resume +import kotlin.random.Random @RunWith(AndroidJUnit4::class) @LargeTest @@ -66,7 +67,7 @@ class VerifySessionInteractiveTest : VerificationTestBase() { @Before fun createSessionWithCrossSigning() { val matrix = getMatrixInstance() - val userName = "foobar_${System.currentTimeMillis()}" + val userName = "foobar_${Random.nextLong()}" existingSession = createAccountAndSync(matrix, userName, password, true) doSync { existingSession!!.cryptoService().crossSigningService() @@ -81,7 +82,8 @@ class VerifySessionInteractiveTest : VerificationTestBase() { ) ) } - }, it) + }, it + ) } } diff --git a/vector/src/androidTest/java/im/vector/app/VerifySessionPassphraseTest.kt b/vector/src/androidTest/java/im/vector/app/VerifySessionPassphraseTest.kt index 80d8315a0e..7efae073e5 100644 --- a/vector/src/androidTest/java/im/vector/app/VerifySessionPassphraseTest.kt +++ b/vector/src/androidTest/java/im/vector/app/VerifySessionPassphraseTest.kt @@ -53,6 +53,7 @@ import org.matrix.android.sdk.api.auth.registration.RegistrationFlowResponse import org.matrix.android.sdk.api.session.Session import kotlin.coroutines.Continuation import kotlin.coroutines.resume +import kotlin.random.Random @RunWith(AndroidJUnit4::class) @LargeTest @@ -68,7 +69,7 @@ class VerifySessionPassphraseTest : VerificationTestBase() { fun createSessionWithCrossSigningAnd4S() { val context = InstrumentationRegistry.getInstrumentation().targetContext val matrix = getMatrixInstance() - val userName = "foobar_${System.currentTimeMillis()}" + val userName = "foobar_${Random.nextLong()}" existingSession = createAccountAndSync(matrix, userName, password, true) doSync { existingSession!!.cryptoService().crossSigningService() @@ -83,27 +84,30 @@ class VerifySessionPassphraseTest : VerificationTestBase() { ) ) } - }, it) + }, it + ) } val task = BootstrapCrossSigningTask(existingSession!!, StringProvider(context.resources)) runBlocking { - task.execute(Params( - userInteractiveAuthInterceptor = object : UserInteractiveAuthInterceptor { - override fun performStage(flowResponse: RegistrationFlowResponse, errCode: String?, promise: Continuation) { - promise.resume( - UserPasswordAuth( - user = existingSession!!.myUserId, - password = password, - session = flowResponse.session + task.execute( + Params( + userInteractiveAuthInterceptor = object : UserInteractiveAuthInterceptor { + override fun performStage(flowResponse: RegistrationFlowResponse, errCode: String?, promise: Continuation) { + promise.resume( + UserPasswordAuth( + user = existingSession!!.myUserId, + password = password, + session = flowResponse.session + ) ) - ) - } - }, - passphrase = passphrase, - setupMode = SetupMode.NORMAL - )) + } + }, + passphrase = passphrase, + setupMode = SetupMode.NORMAL + ) + ) } } diff --git a/vector/src/androidTest/java/im/vector/app/core/utils/TestMatrixHelper.kt b/vector/src/androidTest/java/im/vector/app/core/utils/TestMatrixHelper.kt index 322f5fa23d..48fc1343b1 100644 --- a/vector/src/androidTest/java/im/vector/app/core/utils/TestMatrixHelper.kt +++ b/vector/src/androidTest/java/im/vector/app/core/utils/TestMatrixHelper.kt @@ -26,5 +26,5 @@ fun getMatrixInstance(): Matrix { val configuration = MatrixConfiguration( roomDisplayNameFallbackProvider = VectorRoomDisplayNameFallbackProvider(context) ) - return Matrix.createInstance(context, configuration) + return Matrix(context, configuration) } diff --git a/vector/src/androidTest/java/im/vector/app/espresso/tools/EspressoPreference.kt b/vector/src/androidTest/java/im/vector/app/espresso/tools/EspressoPreference.kt index 178b9fb9f6..19dc1d391c 100644 --- a/vector/src/androidTest/java/im/vector/app/espresso/tools/EspressoPreference.kt +++ b/vector/src/androidTest/java/im/vector/app/espresso/tools/EspressoPreference.kt @@ -26,6 +26,7 @@ import androidx.test.espresso.action.ViewActions.click import androidx.test.espresso.contrib.RecyclerViewActions.actionOnItem import androidx.test.espresso.matcher.PreferenceMatchers.withKey import androidx.test.espresso.matcher.ViewMatchers.hasDescendant +import androidx.test.espresso.matcher.ViewMatchers.isFocusable import androidx.test.espresso.matcher.ViewMatchers.withClassName import androidx.test.espresso.matcher.ViewMatchers.withId import androidx.test.espresso.matcher.ViewMatchers.withText @@ -36,8 +37,15 @@ import org.hamcrest.Matchers.`is` fun clickOnPreference(@StringRes textResId: Int) { onView(withId(R.id.recycler_view)) - .perform(actionOnItem( - hasDescendant(withText(textResId)), click())) + .perform( + actionOnItem( + allOf( + hasDescendant(withText(textResId)), + // Avoid to click on the Preference Category + isFocusable() + ), click() + ) + ) } fun clickOnSwitchPreference(preferenceKey: String) { diff --git a/vector/src/androidTest/java/im/vector/app/espresso/tools/ScreenshotFailureRule.kt b/vector/src/androidTest/java/im/vector/app/espresso/tools/ScreenshotFailureRule.kt index 2939dcf4e0..b01c1a895f 100644 --- a/vector/src/androidTest/java/im/vector/app/espresso/tools/ScreenshotFailureRule.kt +++ b/vector/src/androidTest/java/im/vector/app/espresso/tools/ScreenshotFailureRule.kt @@ -24,6 +24,7 @@ import android.os.Build import android.os.Environment import android.provider.MediaStore import androidx.test.platform.app.InstrumentationRegistry.getInstrumentation +import im.vector.app.core.time.DefaultClock import org.junit.rules.TestWatcher import org.junit.runner.Description import timber.log.Timber @@ -54,7 +55,7 @@ private fun storeFailureScreenshot(bitmap: Bitmap, screenshotName: String) { val contentValues = ContentValues().apply { put(MediaStore.Images.Media.MIME_TYPE, "image/jpeg") - put(MediaStore.Images.Media.DATE_TAKEN, System.currentTimeMillis()) + put(MediaStore.Images.Media.DATE_TAKEN, DefaultClock().epochMillis()) } if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { useMediaStoreScreenshotStorage( diff --git a/vector/src/androidTest/java/im/vector/app/ui/UiAllScreensSanityTest.kt b/vector/src/androidTest/java/im/vector/app/ui/UiAllScreensSanityTest.kt index 5a03d5890a..4333558e7a 100644 --- a/vector/src/androidTest/java/im/vector/app/ui/UiAllScreensSanityTest.kt +++ b/vector/src/androidTest/java/im/vector/app/ui/UiAllScreensSanityTest.kt @@ -72,7 +72,6 @@ class UiAllScreensSanityTest { notifications { crawl() } preferences { crawl() } voiceAndVideo() - ignoredUsers() securityAndPrivacy { crawl() } labs() advancedSettings { crawl() } diff --git a/vector/src/androidTest/java/im/vector/app/ui/robot/ElementRobot.kt b/vector/src/androidTest/java/im/vector/app/ui/robot/ElementRobot.kt index 3c5de8b221..b9292bd916 100644 --- a/vector/src/androidTest/java/im/vector/app/ui/robot/ElementRobot.kt +++ b/vector/src/androidTest/java/im/vector/app/ui/robot/ElementRobot.kt @@ -120,6 +120,26 @@ class ElementRobot { .perform(ViewActions.closeSoftKeyboard(), click()) } } + // at this point we are in a race with the app restarting. The steps that happen are: + // - (initially) app has started, app has initial synched + // - (restart) app has strted, app has not initial synched + // - (racey) app shows some UI but overlays with initial sync ui + // - (initial sync finishes) app has started, has initial synched + + // We need to wait for the initial sync to complete; but we can't + // use waitForHome() like login does. + + // waitForHome() -- does not work because we have already fufilled the initialSync + // so we can racily have an IllegalStateException that we have transitioned from busy -> idle + // but never having sent the signal. + + // So we need to not start waiting for an initial sync until we have restarted + // then we do need to wait for the sync to complete. + + // Which is convoluted especially as it involves the app state refreshing + // so; in order to make this be more stable + // I hereby cheat and write: + Thread.sleep(30_000) } else -> { } diff --git a/vector/src/androidTest/java/im/vector/app/ui/robot/MessageMenuRobot.kt b/vector/src/androidTest/java/im/vector/app/ui/robot/MessageMenuRobot.kt index 5c9ecfdef5..53ad2af7e6 100644 --- a/vector/src/androidTest/java/im/vector/app/ui/robot/MessageMenuRobot.kt +++ b/vector/src/androidTest/java/im/vector/app/ui/robot/MessageMenuRobot.kt @@ -75,6 +75,7 @@ class MessageMenuRobot( clickOn(R.string.reply_in_thread) autoClosed = true } + fun viewInRoom() { clickOn(R.string.view_in_room) autoClosed = true diff --git a/vector/src/androidTest/java/im/vector/app/ui/robot/RoomDetailRobot.kt b/vector/src/androidTest/java/im/vector/app/ui/robot/RoomDetailRobot.kt index 0ea1191fba..91409582d9 100644 --- a/vector/src/androidTest/java/im/vector/app/ui/robot/RoomDetailRobot.kt +++ b/vector/src/androidTest/java/im/vector/app/ui/robot/RoomDetailRobot.kt @@ -56,10 +56,8 @@ class RoomDetailRobot { // Menu openMenu() pressBack() - /* TODO something has changed in the menu :/ clickMenu(R.id.voice_call) pressBack() - */ clickMenu(R.id.video_call) pressBack() } diff --git a/vector/src/androidTest/java/im/vector/app/ui/robot/settings/SettingsRobot.kt b/vector/src/androidTest/java/im/vector/app/ui/robot/settings/SettingsRobot.kt index 97aee7ac4a..1697fe078d 100644 --- a/vector/src/androidTest/java/im/vector/app/ui/robot/settings/SettingsRobot.kt +++ b/vector/src/androidTest/java/im/vector/app/ui/robot/settings/SettingsRobot.kt @@ -44,15 +44,11 @@ class SettingsRobot { clickOnAndGoBack(R.string.preference_voice_and_video) { block() } } - fun ignoredUsers(block: () -> Unit = {}) { - clickOnAndGoBack(R.string.settings_ignored_users) { block() } - } - fun securityAndPrivacy(block: SettingsSecurityRobot.() -> Unit) { clickOnAndGoBack(R.string.settings_security_and_privacy) { block(SettingsSecurityRobot()) } } - fun labs(shouldGoBack: Boolean = true, block: () -> Unit = {}) { + fun labs(shouldGoBack: Boolean = true, block: () -> Unit = {}) { if (shouldGoBack) { clickOnAndGoBack(R.string.room_settings_labs_pref_title) { block() } } else { diff --git a/vector/src/androidTest/java/im/vector/app/ui/robot/settings/SettingsSecurityRobot.kt b/vector/src/androidTest/java/im/vector/app/ui/robot/settings/SettingsSecurityRobot.kt index 168db3e0e9..841c99e1b5 100644 --- a/vector/src/androidTest/java/im/vector/app/ui/robot/settings/SettingsSecurityRobot.kt +++ b/vector/src/androidTest/java/im/vector/app/ui/robot/settings/SettingsSecurityRobot.kt @@ -36,5 +36,8 @@ class SettingsSecurityRobot { clickOnPreference(R.string.settings_opt_in_of_analytics) Espresso.pressBack() + + clickOnPreference(R.string.settings_ignored_users) + Espresso.pressBack() } } diff --git a/vector/src/debug/java/im/vector/app/features/debug/DebugMenuActivity.kt b/vector/src/debug/java/im/vector/app/features/debug/DebugMenuActivity.kt index cc69cfd426..d407fcb1f2 100644 --- a/vector/src/debug/java/im/vector/app/features/debug/DebugMenuActivity.kt +++ b/vector/src/debug/java/im/vector/app/features/debug/DebugMenuActivity.kt @@ -29,6 +29,7 @@ import im.vector.app.R import im.vector.app.core.di.ActiveSessionHolder import im.vector.app.core.extensions.registerStartForActivityResult import im.vector.app.core.platform.VectorBaseActivity +import im.vector.app.core.time.Clock import im.vector.app.core.utils.PERMISSIONS_FOR_TAKING_PHOTO import im.vector.app.core.utils.checkPermissions import im.vector.app.core.utils.registerForPermissionsResult @@ -60,6 +61,9 @@ class DebugMenuActivity : VectorBaseActivity() { @Inject lateinit var activeSessionHolder: ActiveSessionHolder + @Inject + lateinit var clock: Clock + private lateinit var buffer: ByteArray override fun initUiAndData() { @@ -165,7 +169,7 @@ class DebugMenuActivity : VectorBaseActivity() { } val builder = NotificationCompat.Builder(this, "CHAN") - .setWhen(System.currentTimeMillis()) + .setWhen(clock.epochMillis()) .setContentTitle("Title") .setContentText("Content") // No effect because it's a group summary notif @@ -180,16 +184,16 @@ class DebugMenuActivity : VectorBaseActivity() { .setName("User name") .build() ) - .addMessage("Message 1 - 1", System.currentTimeMillis(), Person.Builder().setName("user 1-1").build()) - .addMessage("Message 1 - 2", System.currentTimeMillis(), Person.Builder().setName("user 1-2").build()) + .addMessage("Message 1 - 1", clock.epochMillis(), Person.Builder().setName("user 1-1").build()) + .addMessage("Message 1 - 2", clock.epochMillis(), Person.Builder().setName("user 1-2").build()) val messagingStyle2 = NotificationCompat.MessagingStyle( Person.Builder() .setName("User name 2") .build() ) - .addMessage("Message 2 - 1", System.currentTimeMillis(), Person.Builder().setName("user 1-1").build()) - .addMessage("Message 2 - 2", System.currentTimeMillis(), Person.Builder().setName("user 1-2").build()) + .addMessage("Message 2 - 1", clock.epochMillis(), Person.Builder().setName("user 1-1").build()) + .addMessage("Message 2 - 2", clock.epochMillis(), Person.Builder().setName("user 1-2").build()) notificationManager.notify(10, builder.build()) @@ -197,7 +201,7 @@ class DebugMenuActivity : VectorBaseActivity() { 11, NotificationCompat.Builder(this, "CHAN") .setChannelId("CHAN") - .setWhen(System.currentTimeMillis()) + .setWhen(clock.epochMillis()) .setContentTitle("Title 1") .setContentText("Content 1") // For shortcut on long press on launcher icon @@ -211,7 +215,7 @@ class DebugMenuActivity : VectorBaseActivity() { notificationManager.notify( 12, NotificationCompat.Builder(this, "CHAN2") - .setWhen(System.currentTimeMillis()) + .setWhen(clock.epochMillis()) .setContentTitle("Title 2") .setContentText("Content 2") .setStyle(messagingStyle2) @@ -243,8 +247,10 @@ class DebugMenuActivity : VectorBaseActivity() { private val qrStartForActivityResult = registerStartForActivityResult { activityResult -> if (activityResult.resultCode == Activity.RESULT_OK) { - toast("QrCode: " + QrCodeScannerActivity.getResultText(activityResult.data) + - " is QRCode: " + QrCodeScannerActivity.getResultIsQrCode(activityResult.data)) + toast( + "QrCode: " + QrCodeScannerActivity.getResultText(activityResult.data) + + " is QRCode: " + QrCodeScannerActivity.getResultIsQrCode(activityResult.data) + ) // Also update the current QR Code (reverse operation) // renderQrCode(QrCodeScannerActivity.getResultText(data) ?: "") diff --git a/vector/src/debug/java/im/vector/app/features/debug/DebugPermissionActivity.kt b/vector/src/debug/java/im/vector/app/features/debug/DebugPermissionActivity.kt index a35bb40f8f..0f00f2daa5 100644 --- a/vector/src/debug/java/im/vector/app/features/debug/DebugPermissionActivity.kt +++ b/vector/src/debug/java/im/vector/app/features/debug/DebugPermissionActivity.kt @@ -45,7 +45,8 @@ class DebugPermissionActivity : VectorBaseActivity() 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 5b3ee30722..f2904e4b1a 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 @@ -28,43 +28,45 @@ class DebugFeaturesStateFactory @Inject constructor( ) { fun create(): FeaturesState { - return FeaturesState(listOf( - createEnumFeature( - label = "Onboarding variant", - featureOverride = debugFeatures.onboardingVariant(), - featureDefault = defaultFeatures.onboardingVariant() - ), - createBooleanFeature( - label = "FTUE Splash - I already have an account", - key = DebugFeatureKeys.onboardingAlreadyHaveAnAccount, - factory = VectorFeatures::isOnboardingAlreadyHaveAccountSplashEnabled - ), - createBooleanFeature( - label = "FTUE Splash - carousel", - key = DebugFeatureKeys.onboardingSplashCarousel, - factory = VectorFeatures::isOnboardingSplashCarouselEnabled - ), - createBooleanFeature( - label = "FTUE Use Case", - key = DebugFeatureKeys.onboardingUseCase, - factory = VectorFeatures::isOnboardingUseCaseEnabled - ), - createBooleanFeature( - label = "FTUE Personalize profile", - key = DebugFeatureKeys.onboardingPersonalize, - factory = VectorFeatures::isOnboardingPersonalizeEnabled - ), - createBooleanFeature( - label = "FTUE Combined register", - key = DebugFeatureKeys.onboardingCombinedRegister, - factory = VectorFeatures::isOnboardingCombinedRegisterEnabled - ), - createBooleanFeature( - label = "Live location sharing", - key = DebugFeatureKeys.liveLocationSharing, - factory = VectorFeatures::isLiveLocationEnabled - ), - )) + return FeaturesState( + listOf( + createEnumFeature( + label = "Onboarding variant", + featureOverride = debugFeatures.onboardingVariant(), + featureDefault = defaultFeatures.onboardingVariant() + ), + createBooleanFeature( + label = "FTUE Splash - I already have an account", + key = DebugFeatureKeys.onboardingAlreadyHaveAnAccount, + factory = VectorFeatures::isOnboardingAlreadyHaveAccountSplashEnabled + ), + createBooleanFeature( + label = "FTUE Splash - carousel", + key = DebugFeatureKeys.onboardingSplashCarousel, + factory = VectorFeatures::isOnboardingSplashCarouselEnabled + ), + createBooleanFeature( + label = "FTUE Use Case", + key = DebugFeatureKeys.onboardingUseCase, + factory = VectorFeatures::isOnboardingUseCaseEnabled + ), + createBooleanFeature( + label = "FTUE Personalize profile", + key = DebugFeatureKeys.onboardingPersonalize, + factory = VectorFeatures::isOnboardingPersonalizeEnabled + ), + createBooleanFeature( + label = "FTUE Combined register", + key = DebugFeatureKeys.onboardingCombinedRegister, + factory = VectorFeatures::isOnboardingCombinedRegisterEnabled + ), + createBooleanFeature( + label = "Live location sharing", + key = DebugFeatureKeys.liveLocationSharing, + factory = VectorFeatures::isLiveLocationEnabled + ), + ) + ) } private fun createBooleanFeature(key: Preferences.Key, label: String, factory: KFunction1): Feature { 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 bc716779ac..07fab8a58d 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 @@ -60,6 +60,9 @@ class DebugVectorFeatures( override fun isLiveLocationEnabled(): Boolean = read(DebugFeatureKeys.liveLocationSharing) ?: vectorFeatures.isLiveLocationEnabled() + override fun isScreenSharingEnabled(): Boolean = read(DebugFeatureKeys.screenSharing) + ?: vectorFeatures.isScreenSharingEnabled() + fun override(value: T?, key: Preferences.Key) = updatePreferences { if (value == null) { it.remove(key) @@ -114,4 +117,5 @@ object DebugFeatureKeys { val onboardingPersonalize = booleanPreferencesKey("onboarding-personalize") val onboardingCombinedRegister = booleanPreferencesKey("onboarding-combined-register") val liveLocationSharing = booleanPreferencesKey("live-location-sharing") + val screenSharing = booleanPreferencesKey("screen-sharing") } diff --git a/vector/src/debug/java/im/vector/app/features/debug/sas/SasEmojiItem.kt b/vector/src/debug/java/im/vector/app/features/debug/sas/SasEmojiItem.kt index a7b74f3b59..9e0c013960 100644 --- a/vector/src/debug/java/im/vector/app/features/debug/sas/SasEmojiItem.kt +++ b/vector/src/debug/java/im/vector/app/features/debug/sas/SasEmojiItem.kt @@ -32,6 +32,7 @@ abstract class SasEmojiItem : VectorEpoxyModel() { @EpoxyAttribute var index: Int = 0 + @EpoxyAttribute lateinit var emojiRepresentation: EmojiRepresentation diff --git a/vector/src/debug/java/im/vector/app/features/debug/settings/DebugPrivateSettingsViewModel.kt b/vector/src/debug/java/im/vector/app/features/debug/settings/DebugPrivateSettingsViewModel.kt index 1d77d031af..dfe412117f 100644 --- a/vector/src/debug/java/im/vector/app/features/debug/settings/DebugPrivateSettingsViewModel.kt +++ b/vector/src/debug/java/im/vector/app/features/debug/settings/DebugPrivateSettingsViewModel.kt @@ -57,10 +57,12 @@ class DebugPrivateSettingsViewModel @AssistedInject constructor( debugVectorOverrides.forceHomeserverCapabilities.setOnEach { val activeDisplayNameOption = BooleanHomeserverCapabilitiesOverride.from(it.canChangeDisplayName) val activeAvatarOption = BooleanHomeserverCapabilitiesOverride.from(it.canChangeAvatar) - copy(homeserverCapabilityOverrides = homeserverCapabilityOverrides.copy( - displayName = homeserverCapabilityOverrides.displayName.copy(activeOption = activeDisplayNameOption), - avatar = homeserverCapabilityOverrides.avatar.copy(activeOption = activeAvatarOption), - )) + copy( + homeserverCapabilityOverrides = homeserverCapabilityOverrides.copy( + displayName = homeserverCapabilityOverrides.displayName.copy(activeOption = activeDisplayNameOption), + avatar = homeserverCapabilityOverrides.avatar.copy(activeOption = activeAvatarOption), + ) + ) } } diff --git a/vector/src/fdroid/java/im/vector/app/fdroid/BackgroundSyncStarter.kt b/vector/src/fdroid/java/im/vector/app/fdroid/BackgroundSyncStarter.kt index 0fe89e7fe5..e890180c4f 100644 --- a/vector/src/fdroid/java/im/vector/app/fdroid/BackgroundSyncStarter.kt +++ b/vector/src/fdroid/java/im/vector/app/fdroid/BackgroundSyncStarter.kt @@ -18,13 +18,17 @@ package im.vector.app.fdroid import android.content.Context import im.vector.app.core.di.ActiveSessionHolder +import im.vector.app.core.time.Clock import im.vector.app.fdroid.receiver.AlarmSyncBroadcastReceiver import im.vector.app.features.settings.BackgroundSyncMode import im.vector.app.features.settings.VectorPreferences import timber.log.Timber object BackgroundSyncStarter { - fun start(context: Context, vectorPreferences: VectorPreferences, activeSessionHolder: ActiveSessionHolder) { + fun start(context: Context, + vectorPreferences: VectorPreferences, + activeSessionHolder: ActiveSessionHolder, + clock: Clock) { if (vectorPreferences.areNotificationEnabledForDevice()) { val activeSession = activeSessionHolder.getSafeActiveSession() ?: return when (vectorPreferences.getFdroidSyncBackgroundMode()) { @@ -38,7 +42,12 @@ object BackgroundSyncStarter { } BackgroundSyncMode.FDROID_BACKGROUND_SYNC_MODE_FOR_REALTIME -> { // We need to use alarm in this mode - AlarmSyncBroadcastReceiver.scheduleAlarm(context, activeSession.sessionId, vectorPreferences.backgroundSyncDelay()) + AlarmSyncBroadcastReceiver.scheduleAlarm( + context, + activeSession.sessionId, + vectorPreferences.backgroundSyncDelay(), + clock + ) Timber.i("## Sync: Alarm scheduled to start syncing") } BackgroundSyncMode.FDROID_BACKGROUND_SYNC_MODE_DISABLED -> { diff --git a/vector/src/fdroid/java/im/vector/app/fdroid/features/settings/troubleshoot/TestAutoStartBoot.kt b/vector/src/fdroid/java/im/vector/app/fdroid/features/settings/troubleshoot/TestAutoStartBoot.kt index 27a3f09ddc..6d741a36ba 100644 --- a/vector/src/fdroid/java/im/vector/app/fdroid/features/settings/troubleshoot/TestAutoStartBoot.kt +++ b/vector/src/fdroid/java/im/vector/app/fdroid/features/settings/troubleshoot/TestAutoStartBoot.kt @@ -28,7 +28,7 @@ import javax.inject.Inject */ class TestAutoStartBoot @Inject constructor(private val vectorPreferences: VectorPreferences, private val stringProvider: StringProvider) : - TroubleshootTest(R.string.settings_troubleshoot_test_service_boot_title) { + TroubleshootTest(R.string.settings_troubleshoot_test_service_boot_title) { override fun perform(activityResultLauncher: ActivityResultLauncher) { if (vectorPreferences.autoStartOnBoot()) { diff --git a/vector/src/fdroid/java/im/vector/app/fdroid/features/settings/troubleshoot/TestBackgroundRestrictions.kt b/vector/src/fdroid/java/im/vector/app/fdroid/features/settings/troubleshoot/TestBackgroundRestrictions.kt index b5635e186c..5932d2c372 100644 --- a/vector/src/fdroid/java/im/vector/app/fdroid/features/settings/troubleshoot/TestBackgroundRestrictions.kt +++ b/vector/src/fdroid/java/im/vector/app/fdroid/features/settings/troubleshoot/TestBackgroundRestrictions.kt @@ -28,7 +28,7 @@ import javax.inject.Inject class TestBackgroundRestrictions @Inject constructor(private val context: FragmentActivity, private val stringProvider: StringProvider) : - TroubleshootTest(R.string.settings_troubleshoot_test_bg_restricted_title) { + TroubleshootTest(R.string.settings_troubleshoot_test_bg_restricted_title) { override fun perform(activityResultLauncher: ActivityResultLauncher) { context.getSystemService()!!.apply { @@ -39,24 +39,30 @@ class TestBackgroundRestrictions @Inject constructor(private val context: Fragme ConnectivityManagerCompat.RESTRICT_BACKGROUND_STATUS_ENABLED -> { // Background data usage is blocked for this app. Wherever possible, // the app should also use less data in the foreground. - description = stringProvider.getString(R.string.settings_troubleshoot_test_bg_restricted_failed, - "RESTRICT_BACKGROUND_STATUS_ENABLED") + description = stringProvider.getString( + R.string.settings_troubleshoot_test_bg_restricted_failed, + "RESTRICT_BACKGROUND_STATUS_ENABLED" + ) status = TestStatus.FAILED quickFix = null } ConnectivityManagerCompat.RESTRICT_BACKGROUND_STATUS_WHITELISTED -> { // The app is whitelisted. Wherever possible, // the app should use less data in the foreground and background. - description = stringProvider.getString(R.string.settings_troubleshoot_test_bg_restricted_success, - "RESTRICT_BACKGROUND_STATUS_WHITELISTED") + description = stringProvider.getString( + R.string.settings_troubleshoot_test_bg_restricted_success, + "RESTRICT_BACKGROUND_STATUS_WHITELISTED" + ) status = TestStatus.SUCCESS quickFix = null } ConnectivityManagerCompat.RESTRICT_BACKGROUND_STATUS_DISABLED -> { // Data Saver is disabled. Since the device is connected to a // metered network, the app should use less data wherever possible. - description = stringProvider.getString(R.string.settings_troubleshoot_test_bg_restricted_success, - "RESTRICT_BACKGROUND_STATUS_DISABLED") + description = stringProvider.getString( + R.string.settings_troubleshoot_test_bg_restricted_success, + "RESTRICT_BACKGROUND_STATUS_DISABLED" + ) status = TestStatus.SUCCESS quickFix = null } diff --git a/vector/src/fdroid/java/im/vector/app/fdroid/features/settings/troubleshoot/TestBatteryOptimization.kt b/vector/src/fdroid/java/im/vector/app/fdroid/features/settings/troubleshoot/TestBatteryOptimization.kt index a5154c7483..57bdf721a2 100644 --- a/vector/src/fdroid/java/im/vector/app/fdroid/features/settings/troubleshoot/TestBatteryOptimization.kt +++ b/vector/src/fdroid/java/im/vector/app/fdroid/features/settings/troubleshoot/TestBatteryOptimization.kt @@ -31,7 +31,7 @@ class TestBatteryOptimization @Inject constructor( ) : TroubleshootTest(R.string.settings_troubleshoot_test_battery_title) { override fun perform(activityResultLauncher: ActivityResultLauncher) { - if (isIgnoringBatteryOptimizations(context)) { + if (context.isIgnoringBatteryOptimizations()) { description = stringProvider.getString(R.string.settings_troubleshoot_test_battery_success) status = TestStatus.SUCCESS quickFix = null diff --git a/vector/src/fdroid/java/im/vector/app/fdroid/receiver/AlarmSyncBroadcastReceiver.kt b/vector/src/fdroid/java/im/vector/app/fdroid/receiver/AlarmSyncBroadcastReceiver.kt index 4be36d7de3..09bd56654d 100644 --- a/vector/src/fdroid/java/im/vector/app/fdroid/receiver/AlarmSyncBroadcastReceiver.kt +++ b/vector/src/fdroid/java/im/vector/app/fdroid/receiver/AlarmSyncBroadcastReceiver.kt @@ -27,6 +27,7 @@ import androidx.core.content.getSystemService import im.vector.app.core.extensions.singletonEntryPoint import im.vector.app.core.platform.PendingIntentCompat import im.vector.app.core.services.VectorSyncService +import im.vector.app.core.time.Clock import org.matrix.android.sdk.api.session.sync.job.SyncService import timber.log.Timber @@ -34,10 +35,13 @@ class AlarmSyncBroadcastReceiver : BroadcastReceiver() { override fun onReceive(context: Context, intent: Intent) { Timber.d("## Sync: AlarmSyncBroadcastReceiver received intent") - val vectorPreferences = context.singletonEntryPoint() - .takeIf { it.activeSessionHolder().getSafeActiveSession() != null } - ?.vectorPreferences() - ?: return Unit.also { Timber.v("No active session, so don't launch sync service.") } + val singletonEntryPoint = context.singletonEntryPoint() + if (singletonEntryPoint.activeSessionHolder().getSafeActiveSession() == null) { + Timber.v("No active session, so don't launch sync service.") + return + } + val vectorPreferences = singletonEntryPoint.vectorPreferences() + val clock = singletonEntryPoint.clock() val sessionId = intent.getStringExtra(SyncService.EXTRA_SESSION_ID) ?: return VectorSyncService.newPeriodicIntent( @@ -52,7 +56,7 @@ class AlarmSyncBroadcastReceiver : BroadcastReceiver() { ContextCompat.startForegroundService(context, it) } catch (ex: Throwable) { Timber.i("## Sync: Failed to start service, Alarm scheduled to restart service") - scheduleAlarm(context, sessionId, vectorPreferences.backgroundSyncDelay()) + scheduleAlarm(context, sessionId, vectorPreferences.backgroundSyncDelay(), clock) Timber.e(ex) } } @@ -61,7 +65,7 @@ class AlarmSyncBroadcastReceiver : BroadcastReceiver() { companion object { private const val REQUEST_CODE = 0 - fun scheduleAlarm(context: Context, sessionId: String, delayInSeconds: Int) { + fun scheduleAlarm(context: Context, sessionId: String, delayInSeconds: Int, clock: Clock) { // Reschedule Timber.v("## Sync: Scheduling alarm for background sync in $delayInSeconds seconds") val intent = Intent(context, AlarmSyncBroadcastReceiver::class.java).apply { @@ -74,7 +78,7 @@ class AlarmSyncBroadcastReceiver : BroadcastReceiver() { intent, PendingIntent.FLAG_UPDATE_CURRENT or PendingIntentCompat.FLAG_IMMUTABLE ) - val firstMillis = System.currentTimeMillis() + delayInSeconds * 1000L + val firstMillis = clock.epochMillis() + delayInSeconds * 1000L val alarmMgr = context.getSystemService()!! if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { alarmMgr.setAndAllowWhileIdle(AlarmManager.RTC_WAKEUP, firstMillis, pIntent) diff --git a/vector/src/fdroid/java/im/vector/app/fdroid/receiver/OnApplicationUpgradeOrRebootReceiver.kt b/vector/src/fdroid/java/im/vector/app/fdroid/receiver/OnApplicationUpgradeOrRebootReceiver.kt index 935d7e6e13..aacd7723f5 100644 --- a/vector/src/fdroid/java/im/vector/app/fdroid/receiver/OnApplicationUpgradeOrRebootReceiver.kt +++ b/vector/src/fdroid/java/im/vector/app/fdroid/receiver/OnApplicationUpgradeOrRebootReceiver.kt @@ -29,10 +29,11 @@ class OnApplicationUpgradeOrRebootReceiver : BroadcastReceiver() { override fun onReceive(context: Context, intent: Intent) { Timber.v("## onReceive() ${intent.action}") val singletonEntryPoint = context.singletonEntryPoint() - BackgroundSyncStarter.start( - context, - singletonEntryPoint.vectorPreferences(), - singletonEntryPoint.activeSessionHolder() - ) + BackgroundSyncStarter.start( + context, + singletonEntryPoint.vectorPreferences(), + singletonEntryPoint.activeSessionHolder(), + singletonEntryPoint.clock() + ) } } diff --git a/vector/src/fdroid/java/im/vector/app/push/fcm/FcmHelper.kt b/vector/src/fdroid/java/im/vector/app/push/fcm/FcmHelper.kt index 7603e738d7..425fd1081a 100755 --- a/vector/src/fdroid/java/im/vector/app/push/fcm/FcmHelper.kt +++ b/vector/src/fdroid/java/im/vector/app/push/fcm/FcmHelper.kt @@ -21,6 +21,7 @@ import android.app.Activity import android.content.Context import im.vector.app.core.di.ActiveSessionHolder import im.vector.app.core.pushers.PushersManager +import im.vector.app.core.time.Clock import im.vector.app.fdroid.BackgroundSyncStarter import im.vector.app.fdroid.receiver.AlarmSyncBroadcastReceiver import im.vector.app.features.settings.VectorPreferences @@ -66,7 +67,10 @@ object FcmHelper { AlarmSyncBroadcastReceiver.cancelAlarm(context) } - fun onEnterBackground(context: Context, vectorPreferences: VectorPreferences, activeSessionHolder: ActiveSessionHolder) { - BackgroundSyncStarter.start(context, vectorPreferences, activeSessionHolder) + fun onEnterBackground(context: Context, + vectorPreferences: VectorPreferences, + activeSessionHolder: ActiveSessionHolder, + clock: Clock) { + BackgroundSyncStarter.start(context, vectorPreferences, activeSessionHolder, clock) } } diff --git a/vector/src/gplay/java/im/vector/app/gplay/features/settings/troubleshoot/TestPlayServices.kt b/vector/src/gplay/java/im/vector/app/gplay/features/settings/troubleshoot/TestPlayServices.kt index cc682e7a5f..ecb457bf9f 100644 --- a/vector/src/gplay/java/im/vector/app/gplay/features/settings/troubleshoot/TestPlayServices.kt +++ b/vector/src/gplay/java/im/vector/app/gplay/features/settings/troubleshoot/TestPlayServices.kt @@ -31,7 +31,7 @@ import javax.inject.Inject */ class TestPlayServices @Inject constructor(private val context: FragmentActivity, private val stringProvider: StringProvider) : - TroubleshootTest(R.string.settings_troubleshoot_test_play_services_title) { + TroubleshootTest(R.string.settings_troubleshoot_test_play_services_title) { override fun perform(activityResultLauncher: ActivityResultLauncher) { val apiAvailability = GoogleApiAvailability.getInstance() diff --git a/vector/src/gplay/java/im/vector/app/gplay/features/settings/troubleshoot/TestPushFromPushGateway.kt b/vector/src/gplay/java/im/vector/app/gplay/features/settings/troubleshoot/TestPushFromPushGateway.kt index 7ae68b201b..f485de760a 100644 --- a/vector/src/gplay/java/im/vector/app/gplay/features/settings/troubleshoot/TestPushFromPushGateway.kt +++ b/vector/src/gplay/java/im/vector/app/gplay/features/settings/troubleshoot/TestPushFromPushGateway.kt @@ -41,7 +41,7 @@ class TestPushFromPushGateway @Inject constructor(private val context: FragmentA private val errorFormatter: ErrorFormatter, private val pushersManager: PushersManager, private val activeSessionHolder: ActiveSessionHolder) : - TroubleshootTest(R.string.settings_troubleshoot_test_push_loop_title) { + TroubleshootTest(R.string.settings_troubleshoot_test_push_loop_title) { private var action: Job? = null private var pushReceived: Boolean = false 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 913b5491ea..2db03f3428 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 @@ -37,7 +37,7 @@ class TestTokenRegistration @Inject constructor(private val context: FragmentAct private val stringProvider: StringProvider, private val pushersManager: PushersManager, private val activeSessionHolder: ActiveSessionHolder) : - TroubleshootTest(R.string.settings_troubleshoot_test_token_registration_title) { + TroubleshootTest(R.string.settings_troubleshoot_test_token_registration_title) { override fun perform(activityResultLauncher: ActivityResultLauncher) { // Check if we have a registered pusher for this token @@ -49,12 +49,14 @@ class TestTokenRegistration @Inject constructor(private val context: FragmentAct status = TestStatus.FAILED return } - val pushers = session.getPushers().filter { + val pushers = session.pushersService().getPushers().filter { it.pushKey == fcmToken && it.state == PusherState.REGISTERED } if (pushers.isEmpty()) { - description = stringProvider.getString(R.string.settings_troubleshoot_test_token_registration_failed, - stringProvider.getString(R.string.sas_error_unknown)) + description = stringProvider.getString( + R.string.settings_troubleshoot_test_token_registration_failed, + stringProvider.getString(R.string.sas_error_unknown) + ) quickFix = object : TroubleshootQuickFix(R.string.settings_troubleshoot_test_token_registration_quick_fix) { override fun doFix() { val workId = pushersManager.enqueueRegisterPusherWithFcmKey(fcmToken) diff --git a/vector/src/gplay/java/im/vector/app/gplay/push/fcm/VectorFirebaseMessagingService.kt b/vector/src/gplay/java/im/vector/app/gplay/push/fcm/VectorFirebaseMessagingService.kt index 93a82f24f9..b62520278a 100755 --- a/vector/src/gplay/java/im/vector/app/gplay/push/fcm/VectorFirebaseMessagingService.kt +++ b/vector/src/gplay/java/im/vector/app/gplay/push/fcm/VectorFirebaseMessagingService.kt @@ -46,6 +46,8 @@ import kotlinx.coroutines.runBlocking import org.matrix.android.sdk.api.extensions.tryOrNull import org.matrix.android.sdk.api.logger.LoggerTag import org.matrix.android.sdk.api.session.Session +import org.matrix.android.sdk.api.session.getRoom +import org.matrix.android.sdk.api.session.room.getTimelineEvent import timber.log.Timber import javax.inject.Inject @@ -194,7 +196,7 @@ class VectorFirebaseMessagingService : FirebaseMessagingService() { coroutineScope.launch { Timber.tag(loggerTag.value).d("Fast lane: start request") - val event = tryOrNull { session.getEvent(roomId, eventId) } ?: return@launch + val event = tryOrNull { session.eventService().getEvent(roomId, eventId) } ?: return@launch val resolvedEvent = notifiableEventResolver.resolveInMemoryEvent(session, event, canBeReplaced = true) diff --git a/vector/src/gplay/java/im/vector/app/push/fcm/FcmHelper.kt b/vector/src/gplay/java/im/vector/app/push/fcm/FcmHelper.kt index 52ad4be087..3d44f10f76 100755 --- a/vector/src/gplay/java/im/vector/app/push/fcm/FcmHelper.kt +++ b/vector/src/gplay/java/im/vector/app/push/fcm/FcmHelper.kt @@ -26,6 +26,7 @@ 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.PushersManager +import im.vector.app.core.time.Clock import im.vector.app.features.settings.VectorPreferences import timber.log.Timber @@ -107,7 +108,10 @@ object FcmHelper { } @Suppress("UNUSED_PARAMETER") - fun onEnterBackground(context: Context, vectorPreferences: VectorPreferences, activeSessionHolder: ActiveSessionHolder) { + fun onEnterBackground(context: Context, + vectorPreferences: VectorPreferences, + activeSessionHolder: ActiveSessionHolder, + clock: Clock) { // No op } } diff --git a/vector/src/main/AndroidManifest.xml b/vector/src/main/AndroidManifest.xml index eada664216..20b7c4908a 100644 --- a/vector/src/main/AndroidManifest.xml +++ b/vector/src/main/AndroidManifest.xml @@ -375,6 +375,12 @@ android:exported="false" android:foregroundServiceType="location" /> + + () .map { session.spaceService().getRootSpaceSummaries().size } diff --git a/vector/src/main/java/im/vector/app/AutoRageShaker.kt b/vector/src/main/java/im/vector/app/AutoRageShaker.kt index 195e2f68dc..c77ce2b647 100644 --- a/vector/src/main/java/im/vector/app/AutoRageShaker.kt +++ b/vector/src/main/java/im/vector/app/AutoRageShaker.kt @@ -169,7 +169,7 @@ class AutoRageShaker @Inject constructor( coroutineScope.launch { try { - activeSessionHolder.getSafeActiveSession()?.sendToDevice( + activeSessionHolder.getSafeActiveSession()?.toDeviceService()?.sendToDevice( eventType = AUTO_RS_REQUEST, userId = target.senderUserId, deviceId = target.senderDeviceId, @@ -261,7 +261,7 @@ class AutoRageShaker @Inject constructor( this.currentActiveSessionId = sessionId hasSynced = session.hasAlreadySynced() - session.getSyncStatusLive() + session.syncStatusService().getSyncStatusLive() .asFlow() .onEach { hasSynced = it !is SyncStatusService.Status.InitialSyncProgressing @@ -269,11 +269,11 @@ class AutoRageShaker @Inject constructor( .launchIn(session.coroutineScope) activeSessionIds.add(sessionId) session.addListener(this) - session.addEventStreamListener(detector) + session.eventStreamService().addEventStreamListener(detector) } override fun onSessionStopped(session: Session) { - session.removeEventStreamListener(detector) + session.eventStreamService().removeEventStreamListener(detector) activeSessionIds.remove(session.sessionId) } } diff --git a/vector/src/main/java/im/vector/app/UISIDetector.kt b/vector/src/main/java/im/vector/app/UISIDetector.kt index 6157537743..4a9d8ae266 100644 --- a/vector/src/main/java/im/vector/app/UISIDetector.kt +++ b/vector/src/main/java/im/vector/app/UISIDetector.kt @@ -34,7 +34,7 @@ data class E2EMessageDetected( val senderDeviceId: String, val senderKey: String, val sessionId: String - ) { +) { companion object { fun fromEvent(event: Event, roomId: String): E2EMessageDetected { @@ -52,7 +52,7 @@ data class E2EMessageDetected( } } -class UISIDetector : LiveEventListener { +class UISIDetector(private val timeoutMillis: Long = 30_000L) : LiveEventListener { interface UISIDetectorCallback { val enabled: Boolean @@ -66,7 +66,6 @@ class UISIDetector : LiveEventListener { private val trackedEvents = mutableMapOf() private val executor = Executors.newSingleThreadExecutor() private val timer = Timer() - private val timeoutMillis = 30_000L private val enabled: Boolean get() = callback?.enabled.orFalse() override fun onEventDecrypted(event: Event, clearEvent: JsonDict) { @@ -90,30 +89,35 @@ class UISIDetector : LiveEventListener { val roomId = event.roomId if (!enabled || eventId == null || roomId == null) return - val trackerId: String = trackerId(eventId, roomId) - if (trackedEvents.containsKey(trackerId)) { - Timber.w("## UISIDetector: Event $eventId is already tracked") + val trackedId: String = trackedId(eventId, roomId) + if (trackedEvents.containsKey(trackedId)) { + Timber.v("## UISIDetector: Event $eventId is already tracked") return } // track it and start timer val timeoutTask = object : TimerTask() { override fun run() { executor.execute { + // we should check if it's still tracked (it might have been decrypted) + if (!trackedEvents.containsKey(trackedId)) { + Timber.v("## UISIDetector: E2E error for $eventId was resolved") + return@execute + } unTrack(eventId, roomId) Timber.v("## UISIDetector: Timeout on $eventId") triggerUISI(E2EMessageDetected.fromEvent(event, roomId)) } } } - trackedEvents[trackerId] = timeoutTask + trackedEvents[trackedId] = timeoutTask timer.schedule(timeoutTask, timeoutMillis) } - override fun onLiveEvent(roomId: String, event: Event) { } + override fun onLiveEvent(roomId: String, event: Event) {} - override fun onPaginatedEvent(roomId: String, event: Event) { } + override fun onPaginatedEvent(roomId: String, event: Event) {} - private fun trackerId(eventId: String, roomId: String): String = "$roomId-$eventId" + private fun trackedId(eventId: String, roomId: String): String = "$roomId-$eventId" private fun triggerUISI(source: E2EMessageDetected) { if (!enabled) return @@ -122,6 +126,6 @@ class UISIDetector : LiveEventListener { } private fun unTrack(eventId: String, roomId: String) { - trackedEvents.remove(trackerId(eventId, roomId))?.cancel() + trackedEvents.remove(trackedId(eventId, roomId))?.cancel() } } diff --git a/vector/src/main/java/im/vector/app/VectorApplication.kt b/vector/src/main/java/im/vector/app/VectorApplication.kt index e12eecfefc..a55351f74b 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.core.di.ActiveSessionHolder import im.vector.app.core.extensions.configureAndStart import im.vector.app.core.extensions.startSyncing +import im.vector.app.core.time.Clock import im.vector.app.features.analytics.VectorAnalytics import im.vector.app.features.call.webrtc.WebRtcCallManager import im.vector.app.features.configuration.VectorConfiguration @@ -85,6 +86,7 @@ class VectorApplication : @Inject lateinit var emojiCompatWrapper: EmojiCompatWrapper @Inject lateinit var vectorUncaughtExceptionHandler: VectorUncaughtExceptionHandler @Inject lateinit var activeSessionHolder: ActiveSessionHolder + @Inject lateinit var clock: Clock @Inject lateinit var notificationDrawerManager: NotificationDrawerManager @Inject lateinit var vectorPreferences: VectorPreferences @Inject lateinit var versionProvider: VersionProvider @@ -180,7 +182,7 @@ class VectorApplication : override fun onPause(owner: LifecycleOwner) { Timber.i("App entered background") - FcmHelper.onEnterBackground(appContext, vectorPreferences, activeSessionHolder) + FcmHelper.onEnterBackground(appContext, vectorPreferences, activeSessionHolder, clock) } }) ProcessLifecycleOwner.get().lifecycle.addObserver(appStateHandler) @@ -211,16 +213,18 @@ class VectorApplication : private fun enableStrictModeIfNeeded() { if (BuildConfig.ENABLE_STRICT_MODE_LOGS) { - StrictMode.setThreadPolicy(StrictMode.ThreadPolicy.Builder() - .detectAll() - .penaltyLog() - .build()) + StrictMode.setThreadPolicy( + StrictMode.ThreadPolicy.Builder() + .detectAll() + .penaltyLog() + .build() + ) } } override fun getWorkManagerConfiguration(): WorkConfiguration { return WorkConfiguration.Builder() - .setWorkerFactory(matrix.workerFactory()) + .setWorkerFactory(matrix.getWorkerFactory()) .setExecutor(Executors.newCachedThreadPool()) .build() } diff --git a/vector/src/main/java/im/vector/app/core/contacts/ContactsDataSource.kt b/vector/src/main/java/im/vector/app/core/contacts/ContactsDataSource.kt index db0fab9c42..577533f758 100644 --- a/vector/src/main/java/im/vector/app/core/contacts/ContactsDataSource.kt +++ b/vector/src/main/java/im/vector/app/core/contacts/ContactsDataSource.kt @@ -83,14 +83,16 @@ class ContactsDataSource @Inject constructor( // Get the phone numbers if (withMsisdn) { - contentResolver.query(ContactsContract.CommonDataKinds.Phone.CONTENT_URI, + contentResolver.query( + ContactsContract.CommonDataKinds.Phone.CONTENT_URI, arrayOf( ContactsContract.CommonDataKinds.Phone.CONTACT_ID, ContactsContract.CommonDataKinds.Phone.NUMBER ), null, null, - null) + null + ) ?.use { cursor -> val idColumnIndex = cursor.getColumnIndexOrNull(ContactsContract.CommonDataKinds.Phone.CONTACT_ID) ?: return@use val phoneNumberColumnIndex = cursor.getColumnIndexOrNull(ContactsContract.CommonDataKinds.Phone.NUMBER) ?: return@use @@ -122,7 +124,8 @@ class ContactsDataSource @Inject constructor( ), null, null, - null) + null + ) ?.use { cursor -> val idColumnIndex = cursor.getColumnIndexOrNull(ContactsContract.CommonDataKinds.Email.CONTACT_ID) ?: return@use val emailColumnIndex = cursor.getColumnIndexOrNull(ContactsContract.CommonDataKinds.Email.DATA) ?: return@use diff --git a/vector/src/main/java/im/vector/app/core/datastore/DataStoreProvider.kt b/vector/src/main/java/im/vector/app/core/datastore/DataStoreProvider.kt index 5b7988b76f..3c93b9a7e0 100644 --- a/vector/src/main/java/im/vector/app/core/datastore/DataStoreProvider.kt +++ b/vector/src/main/java/im/vector/app/core/datastore/DataStoreProvider.kt @@ -26,12 +26,12 @@ import kotlin.properties.ReadOnlyProperty import kotlin.reflect.KProperty /** - * Provides a singleton datastore cache - * allows for lazily fetching a datastore instance by key to avoid creating multiple stores for the same file - * Based on https://androidx.tech/artifacts/datastore/datastore-preferences/1.0.0-source/androidx/datastore/preferences/PreferenceDataStoreDelegate.kt.html + * Provides a singleton datastore cache. + * Allows for lazily fetching a datastore instance by key to avoid creating multiple stores for the same file. + * Based on https://androidx.tech/artifacts/datastore/datastore-preferences/1.0.0-source/androidx/datastore/preferences/PreferenceDataStoreDelegate.kt.html. * - * Makes use of a ReadOnlyProperty in order to provide a simplified api on top of a Context - * ReadOnlyProperty allows us to lazily access the backing property instead of requiring it upfront as a dependency + * Makes use of a ReadOnlyProperty in order to provide a simplified api on top of a Context. + * ReadOnlyProperty allows us to lazily access the backing property instead of requiring it upfront as a dependency. *
  * val Context.dataStoreProvider by dataStoreProvider()
  * 
diff --git a/vector/src/main/java/im/vector/app/core/date/DefaultDateFormatterProvider.kt b/vector/src/main/java/im/vector/app/core/date/DefaultDateFormatterProvider.kt index 6371035a17..361868cd95 100644 --- a/vector/src/main/java/im/vector/app/core/date/DefaultDateFormatterProvider.kt +++ b/vector/src/main/java/im/vector/app/core/date/DefaultDateFormatterProvider.kt @@ -24,7 +24,7 @@ import javax.inject.Inject class DefaultDateFormatterProvider @Inject constructor(private val context: Context, private val localeProvider: LocaleProvider) : - DateFormatterProvider { + DateFormatterProvider { override val dateWithMonthFormatter: DateTimeFormatter by lazy { val pattern = DateFormat.getBestDateTimePattern(localeProvider.current(), "d MMMMM") diff --git a/vector/src/main/java/im/vector/app/core/date/VectorDateFormatter.kt b/vector/src/main/java/im/vector/app/core/date/VectorDateFormatter.kt index eb6f6a94f7..b3bd671583 100644 --- a/vector/src/main/java/im/vector/app/core/date/VectorDateFormatter.kt +++ b/vector/src/main/java/im/vector/app/core/date/VectorDateFormatter.kt @@ -22,15 +22,19 @@ import android.text.format.DateUtils import im.vector.app.core.resources.DateProvider import im.vector.app.core.resources.LocaleProvider import im.vector.app.core.resources.toTimestamp +import im.vector.app.core.time.Clock import org.threeten.bp.LocalDateTime import org.threeten.bp.Period import org.threeten.bp.format.DateTimeFormatter import javax.inject.Inject import kotlin.math.absoluteValue -class VectorDateFormatter @Inject constructor(private val context: Context, - private val localeProvider: LocaleProvider, - private val dateFormatterProviders: DateFormatterProviders) { +class VectorDateFormatter @Inject constructor( + private val context: Context, + private val localeProvider: LocaleProvider, + private val dateFormatterProviders: DateFormatterProviders, + private val clock: Clock, +) { private val hourFormatter by lazy { if (DateFormat.is24HourFormat(context)) { @@ -139,7 +143,7 @@ class VectorDateFormatter @Inject constructor(private val context: Context, } /** - * This method will show date and time with a preposition + * This method will show date and time with a preposition. */ private fun formatDateAndTime(ts: Long): String { val date = DateProvider.toLocalDateTime(ts) @@ -153,13 +157,14 @@ class VectorDateFormatter @Inject constructor(private val context: Context, } /** - * We are using this method for the keywords Today/Yesterday + * We are using this method for the keywords Today/Yesterday. */ private fun getRelativeDay(ts: Long): String { return DateUtils.getRelativeTimeSpanString( ts, - System.currentTimeMillis(), + clock.epochMillis(), DateUtils.DAY_IN_MILLIS, - DateUtils.FORMAT_SHOW_WEEKDAY).toString() + DateUtils.FORMAT_SHOW_WEEKDAY + ).toString() } } diff --git a/vector/src/main/java/im/vector/app/core/di/ActiveSessionHolder.kt b/vector/src/main/java/im/vector/app/core/di/ActiveSessionHolder.kt index 4883676f87..983850e3be 100644 --- a/vector/src/main/java/im/vector/app/core/di/ActiveSessionHolder.kt +++ b/vector/src/main/java/im/vector/app/core/di/ActiveSessionHolder.kt @@ -87,7 +87,7 @@ class ActiveSessionHolder @Inject constructor(private val activeSessionDataSourc ?: throw IllegalStateException("You should authenticate before using this") } - // TODO: Stop sync ? + // TODO Stop sync ? // fun switchToSession(sessionParams: SessionParams) { // val newActiveSession = authenticationService.getSession(sessionParams) // activeSession.set(newActiveSession) 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 e3e64063f3..c68a35f4e5 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 @@ -189,7 +189,7 @@ import im.vector.app.features.widgets.WidgetFragment @Module interface FragmentModule { /** - * Fragments with @IntoMap will be injected by this factory + * Fragments with @IntoMap will be injected by this factory. */ @Binds fun bindFragmentFactory(factory: VectorFragmentFactory): FragmentFactory diff --git a/vector/src/main/java/im/vector/app/core/di/HiltMavericksViewModelFactory.kt b/vector/src/main/java/im/vector/app/core/di/HiltMavericksViewModelFactory.kt index 13702053e8..5850cb8cb9 100644 --- a/vector/src/main/java/im/vector/app/core/di/HiltMavericksViewModelFactory.kt +++ b/vector/src/main/java/im/vector/app/core/di/HiltMavericksViewModelFactory.kt @@ -45,7 +45,7 @@ import dagger.hilt.components.SingletonComponent inline fun , S : MavericksState> hiltMavericksViewModelFactory() = HiltMavericksViewModelFactory(VM::class.java) class HiltMavericksViewModelFactory, S : MavericksState>( - private val viewModelClass: Class> + private val viewModelClass: Class> ) : MavericksViewModelFactory { override fun create(viewModelContext: ViewModelContext, state: S): VM { diff --git a/vector/src/main/java/im/vector/app/core/di/ImageManager.kt b/vector/src/main/java/im/vector/app/core/di/ImageManager.kt index 60ce70a0b3..1b2ec5b93a 100644 --- a/vector/src/main/java/im/vector/app/core/di/ImageManager.kt +++ b/vector/src/main/java/im/vector/app/core/di/ImageManager.kt @@ -28,7 +28,7 @@ import java.io.InputStream import javax.inject.Inject /** - * This class is used to configure the library we use for images + * This class is used to configure the library we use for images. */ class ImageManager @Inject constructor( private val context: Context, diff --git a/vector/src/main/java/im/vector/app/core/di/SingletonEntryPoint.kt b/vector/src/main/java/im/vector/app/core/di/SingletonEntryPoint.kt index 283437c679..dc88229a10 100644 --- a/vector/src/main/java/im/vector/app/core/di/SingletonEntryPoint.kt +++ b/vector/src/main/java/im/vector/app/core/di/SingletonEntryPoint.kt @@ -21,6 +21,7 @@ import dagger.hilt.InstallIn import dagger.hilt.components.SingletonComponent import im.vector.app.core.dialogs.UnrecognizedCertificateDialog import im.vector.app.core.error.ErrorFormatter +import im.vector.app.core.time.Clock import im.vector.app.features.analytics.AnalyticsTracker import im.vector.app.features.call.webrtc.WebRtcCallManager import im.vector.app.features.home.AvatarRenderer @@ -46,6 +47,8 @@ interface SingletonEntryPoint { fun navigator(): Navigator + fun clock(): Clock + fun errorFormatter(): ErrorFormatter fun bugReporter(): BugReporter 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 0db7e4e8ea..2945ae7d87 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 @@ -50,6 +50,7 @@ import im.vector.app.features.settings.VectorPreferences import im.vector.app.features.ui.SharedPreferencesUiStateRepository import im.vector.app.features.ui.UiStateRepository import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.DelicateCoroutinesApi import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.GlobalScope import kotlinx.coroutines.SupervisorJob @@ -128,12 +129,12 @@ object VectorStaticModule { @Provides @Singleton fun providesMatrix(context: Context, configuration: MatrixConfiguration): Matrix { - return Matrix.createInstance(context, configuration) + return Matrix(context, configuration) } @Provides fun providesCurrentSession(activeSessionHolder: ActiveSessionHolder): Session { - // TODO: handle session injection better + // TODO handle session injection better return activeSessionHolder.getActiveSession() } @@ -173,7 +174,7 @@ object VectorStaticModule { return CoroutineDispatchers(io = Dispatchers.IO, computation = Dispatchers.Default) } - @Suppress("EXPERIMENTAL_API_USAGE") + @OptIn(DelicateCoroutinesApi::class) @Provides @NamedGlobalScope fun providesGlobalScope(): CoroutineScope { diff --git a/vector/src/main/java/im/vector/app/core/di/ViewModelModule.kt b/vector/src/main/java/im/vector/app/core/di/ViewModelModule.kt index 4f8329c026..d90e934d0a 100644 --- a/vector/src/main/java/im/vector/app/core/di/ViewModelModule.kt +++ b/vector/src/main/java/im/vector/app/core/di/ViewModelModule.kt @@ -49,7 +49,7 @@ import im.vector.app.features.userdirectory.UserListSharedActionViewModel interface ViewModelModule { /** - * ViewModels with @IntoMap will be injected by this factory + * ViewModels with @IntoMap will be injected by this factory. */ @Binds fun bindViewModelFactory(factory: VectorViewModelFactory): ViewModelProvider.Factory diff --git a/vector/src/main/java/im/vector/app/core/dialogs/DialogLocker.kt b/vector/src/main/java/im/vector/app/core/dialogs/DialogLocker.kt index 51a513e11e..d4efbf1388 100644 --- a/vector/src/main/java/im/vector/app/core/dialogs/DialogLocker.kt +++ b/vector/src/main/java/im/vector/app/core/dialogs/DialogLocker.kt @@ -25,7 +25,7 @@ import timber.log.Timber private const val KEY_DIALOG_IS_DISPLAYED = "DialogLocker.KEY_DIALOG_IS_DISPLAYED" /** - * Class to avoid displaying twice the same dialog + * Class to avoid displaying twice the same dialog. */ class DialogLocker(savedInstanceState: Bundle?) : Restorable { diff --git a/vector/src/main/java/im/vector/app/core/dialogs/ExportKeysDialog.kt b/vector/src/main/java/im/vector/app/core/dialogs/ExportKeysDialog.kt index b87b7f0286..024d283c4c 100644 --- a/vector/src/main/java/im/vector/app/core/dialogs/ExportKeysDialog.kt +++ b/vector/src/main/java/im/vector/app/core/dialogs/ExportKeysDialog.kt @@ -35,7 +35,7 @@ class ExportKeysDialog { val textWatcher = object : SimpleTextWatcher() { override fun afterTextChanged(s: Editable) { when { - views.exportDialogEt.text.isNullOrEmpty() -> { + views.exportDialogEt.text.isNullOrEmpty() -> { views.exportDialogSubmit.isEnabled = false views.exportDialogTilConfirm.error = null } @@ -43,7 +43,7 @@ class ExportKeysDialog { views.exportDialogSubmit.isEnabled = true views.exportDialogTilConfirm.error = null } - else -> { + else -> { views.exportDialogSubmit.isEnabled = false views.exportDialogTilConfirm.error = activity.getString(R.string.passphrase_passphrase_does_not_match) } diff --git a/vector/src/main/java/im/vector/app/core/dialogs/GalleryOrCameraDialogHelper.kt b/vector/src/main/java/im/vector/app/core/dialogs/GalleryOrCameraDialogHelper.kt index 8f70808087..a9e223709a 100644 --- a/vector/src/main/java/im/vector/app/core/dialogs/GalleryOrCameraDialogHelper.kt +++ b/vector/src/main/java/im/vector/app/core/dialogs/GalleryOrCameraDialogHelper.kt @@ -27,6 +27,7 @@ import im.vector.app.core.dialogs.GalleryOrCameraDialogHelper.Listener import im.vector.app.core.extensions.insertBeforeLast import im.vector.app.core.extensions.registerStartForActivityResult import im.vector.app.core.resources.ColorProvider +import im.vector.app.core.time.Clock import im.vector.app.core.utils.PERMISSIONS_FOR_TAKING_PHOTO import im.vector.app.core.utils.checkPermissions import im.vector.app.core.utils.onPermissionDeniedDialog @@ -45,7 +46,8 @@ import java.io.File class GalleryOrCameraDialogHelper( // must implement GalleryOrCameraDialogHelper.Listener private val fragment: Fragment, - private val colorProvider: ColorProvider + private val colorProvider: ColorProvider, + private val clock: Clock, ) { interface Listener { fun onImageReady(uri: Uri?) @@ -91,7 +93,7 @@ class GalleryOrCameraDialogHelper( } private fun startUCrop(image: MultiPickerImageType) { - val destinationFile = File(activity.cacheDir, image.displayName.insertBeforeLast("_e_${System.currentTimeMillis()}")) + val destinationFile = File(activity.cacheDir, image.displayName.insertBeforeLast("_e_${clock.epochMillis()}")) val uri = image.contentUri createUCropWithDefaultSettings(colorProvider, uri, destinationFile.toUri(), fragment.getString(R.string.rotate_and_crop_screen_title)) .withAspectRatio(1f, 1f) @@ -107,10 +109,12 @@ class GalleryOrCameraDialogHelper( fun show() { MaterialAlertDialogBuilder(activity) .setTitle(R.string.attachment_type_dialog_title) - .setItems(arrayOf( - fragment.getString(R.string.attachment_type_camera), - fragment.getString(R.string.attachment_type_gallery) - )) { _, which -> + .setItems( + arrayOf( + fragment.getString(R.string.attachment_type_camera), + fragment.getString(R.string.attachment_type_gallery) + ) + ) { _, which -> onAvatarTypeSelected(if (which == 0) Type.Camera else Type.Gallery) } .setPositiveButton(R.string.action_cancel, null) diff --git a/vector/src/main/java/im/vector/app/core/dialogs/UnrecognizedCertificateDialog.kt b/vector/src/main/java/im/vector/app/core/dialogs/UnrecognizedCertificateDialog.kt index 415c82b330..c43b2e4f09 100644 --- a/vector/src/main/java/im/vector/app/core/dialogs/UnrecognizedCertificateDialog.kt +++ b/vector/src/main/java/im/vector/app/core/dialogs/UnrecognizedCertificateDialog.kt @@ -26,7 +26,7 @@ import timber.log.Timber import javax.inject.Inject /** - * This class displays the unknown certificate dialog + * This class displays the unknown certificate dialog. */ class UnrecognizedCertificateDialog @Inject constructor( private val activeSessionHolder: ActiveSessionHolder, @@ -37,7 +37,7 @@ class UnrecognizedCertificateDialog @Inject constructor( /** * Display a certificate dialog box, asking the user about an unknown certificate - * To use when user is currently logged in + * To use when user is currently logged in. * * @param unrecognizedFingerprint the fingerprint for the unknown certificate * @param callback callback to fire when the user makes a decision @@ -60,7 +60,7 @@ class UnrecognizedCertificateDialog @Inject constructor( } /** - * To use during login flow + * To use during login flow. */ fun show(activity: Activity, unrecognizedFingerprint: Fingerprint, @@ -78,7 +78,7 @@ class UnrecognizedCertificateDialog @Inject constructor( } /** - * Display a certificate dialog box, asking the user about an unknown certificate + * Display a certificate dialog box, asking the user about an unknown certificate. * * @param unrecognizedFingerprint the fingerprint for the unknown certificate * @param existing the current session already exist, so it mean that something has changed server side @@ -113,13 +113,17 @@ class UnrecognizedCertificateDialog @Inject constructor( views.sslFingerprintTitle.text = stringProvider.getString(R.string.ssl_fingerprint_hash, unrecognizedFingerprint.hashType.toString()) views.sslFingerprint.text = unrecognizedFingerprint.displayableHexRepr if (userId != null) { - views.sslUserId.text = stringProvider.getString(R.string.generic_label_and_value, + views.sslUserId.text = stringProvider.getString( + R.string.generic_label_and_value, stringProvider.getString(R.string.username), - userId) + userId + ) } else { - views.sslUserId.text = stringProvider.getString(R.string.generic_label_and_value, + views.sslUserId.text = stringProvider.getString( + R.string.generic_label_and_value, stringProvider.getString(R.string.hs_url), - homeServerUrl) + homeServerUrl + ) } if (existing) { if (homeServerConnectionConfigHasFingerprints) { @@ -163,17 +167,17 @@ class UnrecognizedCertificateDialog @Inject constructor( interface Callback { /** - * The certificate was explicitly accepted + * The certificate was explicitly accepted. */ fun onAccept() /** - * The warning was ignored by the user + * The warning was ignored by the user. */ fun onIgnore() /** - * The unknown certificate was explicitly rejected + * The unknown certificate was explicitly rejected. */ fun onReject() } diff --git a/vector/src/main/java/im/vector/app/core/epoxy/Listener.kt b/vector/src/main/java/im/vector/app/core/epoxy/Listener.kt index 4d8f7b3a1c..178728f144 100644 --- a/vector/src/main/java/im/vector/app/core/epoxy/Listener.kt +++ b/vector/src/main/java/im/vector/app/core/epoxy/Listener.kt @@ -21,7 +21,7 @@ import android.widget.TextView import im.vector.app.core.utils.DebouncedClickListener /** - * View.OnClickListener lambda + * View.OnClickListener lambda. */ typealias ClickListener = (View) -> Unit @@ -46,7 +46,7 @@ fun TextView.onLongClickIgnoringLinks(listener: View.OnLongClickListener?) { } /** - * Infer that a Clickable span has been click by the presence of a selection + * Infer that a Clickable span has been click by the presence of a selection. */ private fun hasLongPressedLink() = selectionStart != -1 || selectionEnd != -1 }) @@ -54,6 +54,6 @@ fun TextView.onLongClickIgnoringLinks(listener: View.OnLongClickListener?) { } /** - * Simple Text listener lambda + * Simple Text listener lambda. */ typealias TextListener = (String) -> Unit diff --git a/vector/src/main/java/im/vector/app/core/epoxy/VectorEpoxyModel.kt b/vector/src/main/java/im/vector/app/core/epoxy/VectorEpoxyModel.kt index 6142748bf4..9bd16b09a3 100644 --- a/vector/src/main/java/im/vector/app/core/epoxy/VectorEpoxyModel.kt +++ b/vector/src/main/java/im/vector/app/core/epoxy/VectorEpoxyModel.kt @@ -25,7 +25,7 @@ import kotlinx.coroutines.SupervisorJob import kotlinx.coroutines.cancelChildren /** - * EpoxyModelWithHolder which can listen to visibility state change + * EpoxyModelWithHolder which can listen to visibility state change. */ abstract class VectorEpoxyModel : EpoxyModelWithHolder() { diff --git a/vector/src/main/java/im/vector/app/core/epoxy/profiles/ProfileMatrixItem.kt b/vector/src/main/java/im/vector/app/core/epoxy/profiles/ProfileMatrixItem.kt index 0af342641e..90e81ceb26 100644 --- a/vector/src/main/java/im/vector/app/core/epoxy/profiles/ProfileMatrixItem.kt +++ b/vector/src/main/java/im/vector/app/core/epoxy/profiles/ProfileMatrixItem.kt @@ -32,6 +32,7 @@ abstract class ProfileMatrixItem : BaseProfileMatrixItem(R.id.matrixItemTitle) val subtitleView by bind(R.id.matrixItemSubtitle) + val ignoredUserView by bind(R.id.matrixItemIgnored) val powerLabel by bind(R.id.matrixItemPowerLevelLabel) val presenceImageView by bind(R.id.matrixItemPresenceImageView) val avatarImageView by bind(R.id.matrixItemAvatar) diff --git a/vector/src/main/java/im/vector/app/core/epoxy/profiles/ProfileMatrixItemWithPowerLevel.kt b/vector/src/main/java/im/vector/app/core/epoxy/profiles/ProfileMatrixItemWithPowerLevel.kt index 12189dc8f4..453f402496 100644 --- a/vector/src/main/java/im/vector/app/core/epoxy/profiles/ProfileMatrixItemWithPowerLevel.kt +++ b/vector/src/main/java/im/vector/app/core/epoxy/profiles/ProfileMatrixItemWithPowerLevel.kt @@ -26,11 +26,13 @@ import im.vector.app.core.extensions.setTextOrHide @EpoxyModelClass(layout = R.layout.item_profile_matrix_item) abstract class ProfileMatrixItemWithPowerLevel : ProfileMatrixItem() { + @EpoxyAttribute var ignoredUser: Boolean = false @EpoxyAttribute var powerLevelLabel: CharSequence? = null override fun bind(holder: Holder) { super.bind(holder) holder.editableView.isVisible = false + holder.ignoredUserView.isVisible = ignoredUser holder.powerLabel.setTextOrHide(powerLevelLabel) } } diff --git a/vector/src/main/java/im/vector/app/core/error/ErrorFormatter.kt b/vector/src/main/java/im/vector/app/core/error/ErrorFormatter.kt index 2eb36d758e..a6f80a256e 100644 --- a/vector/src/main/java/im/vector/app/core/error/ErrorFormatter.kt +++ b/vector/src/main/java/im/vector/app/core/error/ErrorFormatter.kt @@ -155,15 +155,17 @@ class DefaultErrorFormatter @Inject constructor( } private fun identityServerError(identityServiceError: IdentityServiceError): String { - return stringProvider.getString(when (identityServiceError) { - IdentityServiceError.OutdatedIdentityServer -> R.string.identity_server_error_outdated_identity_server - IdentityServiceError.OutdatedHomeServer -> R.string.identity_server_error_outdated_home_server - IdentityServiceError.NoIdentityServerConfigured -> R.string.identity_server_error_no_identity_server_configured - IdentityServiceError.TermsNotSignedException -> R.string.identity_server_error_terms_not_signed - IdentityServiceError.BulkLookupSha256NotSupported -> R.string.identity_server_error_bulk_sha256_not_supported - IdentityServiceError.BindingError -> R.string.identity_server_error_binding_error - IdentityServiceError.NoCurrentBindingError -> R.string.identity_server_error_no_current_binding_error - IdentityServiceError.UserConsentNotProvided -> R.string.identity_server_user_consent_not_provided - }) + return stringProvider.getString( + when (identityServiceError) { + IdentityServiceError.OutdatedIdentityServer -> R.string.identity_server_error_outdated_identity_server + IdentityServiceError.OutdatedHomeServer -> R.string.identity_server_error_outdated_home_server + IdentityServiceError.NoIdentityServerConfigured -> R.string.identity_server_error_no_identity_server_configured + IdentityServiceError.TermsNotSignedException -> R.string.identity_server_error_terms_not_signed + IdentityServiceError.BulkLookupSha256NotSupported -> R.string.identity_server_error_bulk_sha256_not_supported + IdentityServiceError.BindingError -> R.string.identity_server_error_binding_error + IdentityServiceError.NoCurrentBindingError -> R.string.identity_server_error_no_current_binding_error + IdentityServiceError.UserConsentNotProvided -> R.string.identity_server_user_consent_not_provided + } + ) } } diff --git a/vector/src/main/java/im/vector/app/core/error/ResourceLimitErrorFormatter.kt b/vector/src/main/java/im/vector/app/core/error/ResourceLimitErrorFormatter.kt index 129491fe15..da9a63d4c0 100644 --- a/vector/src/main/java/im/vector/app/core/error/ResourceLimitErrorFormatter.kt +++ b/vector/src/main/java/im/vector/app/core/error/ResourceLimitErrorFormatter.kt @@ -59,7 +59,7 @@ class ResourceLimitErrorFormatter(private val context: Context) { } /** - * Create a HTML link with a uri + * Create a HTML link with a uri. */ private fun uriAsLink(uri: String): String { val contactStr = context.getString(R.string.resource_limit_contact_admin) diff --git a/vector/src/main/java/im/vector/app/core/extensions/BasicExtensions.kt b/vector/src/main/java/im/vector/app/core/extensions/BasicExtensions.kt index f8b8b83178..63d61af0e6 100644 --- a/vector/src/main/java/im/vector/app/core/extensions/BasicExtensions.kt +++ b/vector/src/main/java/im/vector/app/core/extensions/BasicExtensions.kt @@ -26,12 +26,17 @@ fun Boolean.toOnOff() = if (this) "ON" else "OFF" inline fun T.ooi(block: (T) -> Unit): T = also(block) /** - * Check if a CharSequence is an email + * Check if a CharSequence is an email. */ fun CharSequence.isEmail() = Patterns.EMAIL_ADDRESS.matcher(this).matches() /** - * Check if a CharSequence is a phone number + * Return empty CharSequence if the CharSequence is null. + */ +fun CharSequence?.orEmpty() = this ?: "" + +/** + * Check if a CharSequence is a phone number. */ fun CharSequence.isMsisdn(): Boolean { return try { @@ -48,7 +53,7 @@ fun CharSequence.isMsisdn(): Boolean { * - "file.txt".insertBeforeLast("_foo") will return "file_foo.txt" * - "file".insertBeforeLast("_foo") will return "file_foo" * - "fi.le.txt".insertBeforeLast("_foo") will return "fi.le_foo.txt" - * - null.insertBeforeLast("_foo") will return "_foo" + * - null.insertBeforeLast("_foo") will return "_foo". */ fun String?.insertBeforeLast(insert: String, delimiter: String = "."): String { if (this == null) return insert diff --git a/vector/src/main/java/im/vector/app/core/extensions/Fragment.kt b/vector/src/main/java/im/vector/app/core/extensions/Fragment.kt index a26597809b..dfbd2eba97 100644 --- a/vector/src/main/java/im/vector/app/core/extensions/Fragment.kt +++ b/vector/src/main/java/im/vector/app/core/extensions/Fragment.kt @@ -158,7 +158,7 @@ fun Fragment.addChildFragmentToBackstack( } /** - * Return a list of all child Fragments, recursively + * Return a list of all child Fragments, recursively. */ fun Fragment.getAllChildFragments(): List { return listOf(this) + childFragmentManager.fragments.map { it.getAllChildFragments() }.flatten() diff --git a/vector/src/main/java/im/vector/app/core/extensions/Iterable.kt b/vector/src/main/java/im/vector/app/core/extensions/Iterable.kt index 56a9bbe39f..6eba204f8a 100644 --- a/vector/src/main/java/im/vector/app/core/extensions/Iterable.kt +++ b/vector/src/main/java/im/vector/app/core/extensions/Iterable.kt @@ -36,7 +36,7 @@ inline fun > Iterable.lastMinBy(selector: (T) -> R): T? } /** - * Call each for each item, and between between each items + * Call each for each item, and between between each items. */ inline fun Collection.join(each: (Int, T) -> Unit, between: (Int, T) -> Unit) { val lastIndex = size - 1 diff --git a/vector/src/main/java/im/vector/app/core/extensions/MvRxExtension.kt b/vector/src/main/java/im/vector/app/core/extensions/MvRxExtension.kt index 9daf16a589..26b3e552f9 100644 --- a/vector/src/main/java/im/vector/app/core/extensions/MvRxExtension.kt +++ b/vector/src/main/java/im/vector/app/core/extensions/MvRxExtension.kt @@ -21,7 +21,7 @@ import com.airbnb.mvrx.Fail import com.airbnb.mvrx.Success /** - * It maybe already exist somewhere but I cannot find it + * It maybe already exist somewhere but I cannot find it. */ suspend fun tryAsync(block: suspend () -> T): Async { return try { diff --git a/vector/src/main/java/im/vector/app/core/extensions/RecyclerView.kt b/vector/src/main/java/im/vector/app/core/extensions/RecyclerView.kt index 84cce10d92..eeb5ddce03 100644 --- a/vector/src/main/java/im/vector/app/core/extensions/RecyclerView.kt +++ b/vector/src/main/java/im/vector/app/core/extensions/RecyclerView.kt @@ -25,7 +25,7 @@ import com.airbnb.epoxy.EpoxyController import com.airbnb.epoxy.EpoxyVisibilityTracker /** - * Apply a Vertical LinearLayout Manager to the recyclerView and set the adapter from the epoxy controller + * Apply a Vertical LinearLayout Manager to the recyclerView and set the adapter from the epoxy controller. */ fun RecyclerView.configureWith(epoxyController: EpoxyController, itemAnimator: RecyclerView.ItemAnimator? = null, @@ -57,7 +57,7 @@ fun RecyclerView.configureWith(epoxyController: EpoxyController, } /** - * To call from Fragment.onDestroyView() + * To call from Fragment.onDestroyView(). */ fun RecyclerView.cleanup() { adapter = null diff --git a/vector/src/main/java/im/vector/app/core/extensions/Session.kt b/vector/src/main/java/im/vector/app/core/extensions/Session.kt index 87ed51522f..1884909906 100644 --- a/vector/src/main/java/im/vector/app/core/extensions/Session.kt +++ b/vector/src/main/java/im/vector/app/core/extensions/Session.kt @@ -30,11 +30,11 @@ import timber.log.Timber fun Session.configureAndStart(context: Context, startSyncing: Boolean = true) { Timber.i("Configure and start session for $myUserId") open() - setFilter(FilterService.FilterPreset.ElementFilter) + filterService().setFilter(FilterService.FilterPreset.ElementFilter) if (startSyncing) { startSyncing(context) } - refreshPushers() + pushersService().refreshPushers() context.singletonEntryPoint().webRtcCallManager().checkForProtocolsSupportIfNeeded() } @@ -62,7 +62,7 @@ fun Session.startSyncing(context: Context) { } /** - * Tell is the session has unsaved e2e keys in the backup + * Tell is the session has unsaved e2e keys in the backup. */ fun Session.hasUnsavedKeys(): Boolean { return cryptoService().inboundGroupSessionsCount(false) > 0 && @@ -74,8 +74,8 @@ fun Session.cannotLogoutSafely(): Boolean { return hasUnsavedKeys() || // has local cross signing keys (cryptoService().crossSigningService().allPrivateKeysKnown() && - // That are not backed up - !sharedSecretStorageService.isRecoverySetup()) + // That are not backed up + !sharedSecretStorageService().isRecoverySetup()) } fun Session.vectorStore(context: Context) = VectorSessionStore(context, myUserId) diff --git a/vector/src/main/java/im/vector/app/core/extensions/TextInputLayout.kt b/vector/src/main/java/im/vector/app/core/extensions/TextInputLayout.kt index 4347da71f0..d9b92e78b7 100644 --- a/vector/src/main/java/im/vector/app/core/extensions/TextInputLayout.kt +++ b/vector/src/main/java/im/vector/app/core/extensions/TextInputLayout.kt @@ -23,7 +23,7 @@ import reactivecircus.flowbinding.android.widget.textChanges fun TextInputLayout.editText() = this.editText!! /** - * Detect if a field starts or ends with spaces + * Detect if a field starts or ends with spaces. */ fun TextInputLayout.hasSurroundingSpaces() = editText().text.toString().let { it.trim() != it } diff --git a/vector/src/main/java/im/vector/app/core/extensions/TextView.kt b/vector/src/main/java/im/vector/app/core/extensions/TextView.kt index 0564f2055b..7d8b60ed58 100644 --- a/vector/src/main/java/im/vector/app/core/extensions/TextView.kt +++ b/vector/src/main/java/im/vector/app/core/extensions/TextView.kt @@ -38,7 +38,7 @@ import im.vector.app.core.utils.copyToClipboard import im.vector.app.features.themes.ThemeUtils /** - * Set a text in the TextView, or set visibility to GONE if the text is null + * Set a text in the TextView, or set visibility to GONE if the text is null. */ fun TextView.setTextOrHide(newText: CharSequence?, hideWhenBlank: Boolean = true, vararg relatedViews: View = emptyArray()) { if (newText == null || @@ -53,7 +53,7 @@ fun TextView.setTextOrHide(newText: CharSequence?, hideWhenBlank: Boolean = true } /** - * Set text with a colored part + * Set text with a colored part. * @param fullTextRes the resource id of the full text. Value MUST contains a parameter for string, which will be replaced by the colored part * @param coloredTextRes the resource id of the colored part of the text * @param colorAttribute attribute of the color. Default to colorPrimary @@ -73,7 +73,7 @@ fun TextView.setTextWithColoredPart(@StringRes fullTextRes: Int, } /** - * Set text with a colored part + * Set text with a colored part. * @param fullText The full text. * @param coloredPart The colored part of the text * @param colorAttribute attribute of the color. Default to colorPrimary @@ -134,7 +134,7 @@ fun TextView.clearDrawables() { } /** - * Set long click listener to copy the current text of the TextView to the clipboard and show a Snackbar + * Set long click listener to copy the current text of the TextView to the clipboard and show a Snackbar. */ fun TextView.copyOnLongClick() { setOnLongClickListener { view -> diff --git a/vector/src/main/java/im/vector/app/core/extensions/UrlExtensions.kt b/vector/src/main/java/im/vector/app/core/extensions/UrlExtensions.kt index 749da0d987..e1e745e399 100644 --- a/vector/src/main/java/im/vector/app/core/extensions/UrlExtensions.kt +++ b/vector/src/main/java/im/vector/app/core/extensions/UrlExtensions.kt @@ -17,7 +17,7 @@ package im.vector.app.core.extensions /** - * Ex: "https://matrix.org/" -> "matrix.org" + * Ex: "https://matrix.org/" -> "matrix.org". */ fun String?.toReducedUrl(keepSchema: Boolean = false): String { return (this ?: "") diff --git a/vector/src/main/java/im/vector/app/core/extensions/ViewExtensions.kt b/vector/src/main/java/im/vector/app/core/extensions/ViewExtensions.kt index f9ca8cb57c..625ff15ef7 100644 --- a/vector/src/main/java/im/vector/app/core/extensions/ViewExtensions.kt +++ b/vector/src/main/java/im/vector/app/core/extensions/ViewExtensions.kt @@ -33,7 +33,7 @@ import im.vector.app.R import im.vector.app.features.themes.ThemeUtils /** - * Remove left margin of a SearchView + * Remove left margin of a SearchView. */ fun SearchView.withoutLeftMargin() { (findViewById(R.id.search_edit_frame))?.let { diff --git a/vector/src/main/java/im/vector/app/core/files/FileSaver.kt b/vector/src/main/java/im/vector/app/core/files/FileSaver.kt index 406fa76520..d0f2247f33 100644 --- a/vector/src/main/java/im/vector/app/core/files/FileSaver.kt +++ b/vector/src/main/java/im/vector/app/core/files/FileSaver.kt @@ -32,7 +32,7 @@ import timber.log.Timber import java.io.File /** - * Save a string to a file with Okio + * Save a string to a file with Okio. */ @WorkerThread fun writeToFile(str: String, file: File): Try { @@ -44,7 +44,7 @@ fun writeToFile(str: String, file: File): Try { } /** - * Save a byte array to a file with Okio + * Save a byte array to a file with Okio. */ @WorkerThread fun writeToFile(data: ByteArray, file: File): Try { diff --git a/vector/src/main/java/im/vector/app/core/glide/AvatarPlaceholder.kt b/vector/src/main/java/im/vector/app/core/glide/AvatarPlaceholder.kt index e61f81de55..e7ced6b335 100644 --- a/vector/src/main/java/im/vector/app/core/glide/AvatarPlaceholder.kt +++ b/vector/src/main/java/im/vector/app/core/glide/AvatarPlaceholder.kt @@ -43,7 +43,7 @@ class AvatarPlaceholderModelLoaderFactory(private val context: Context) : ModelL } class AvatarPlaceholderModelLoader(private val context: Context) : - ModelLoader { + ModelLoader { override fun buildLoadData(model: AvatarPlaceholder, width: Int, height: Int, options: Options): ModelLoader.LoadData? { return ModelLoader.LoadData(ObjectKey(model), AvatarPlaceholderDataFetcher(context, model)) @@ -55,7 +55,7 @@ class AvatarPlaceholderModelLoader(private val context: Context) : } class AvatarPlaceholderDataFetcher(context: Context, private val data: AvatarPlaceholder) : - DataFetcher { + DataFetcher { private val avatarRenderer = context.singletonEntryPoint().avatarRenderer() diff --git a/vector/src/main/java/im/vector/app/core/glide/ElementToDecryptOption.kt b/vector/src/main/java/im/vector/app/core/glide/ElementToDecryptOption.kt index f4f854406e..04b351515a 100644 --- a/vector/src/main/java/im/vector/app/core/glide/ElementToDecryptOption.kt +++ b/vector/src/main/java/im/vector/app/core/glide/ElementToDecryptOption.kt @@ -22,4 +22,5 @@ import org.matrix.android.sdk.api.session.crypto.attachments.ElementToDecrypt const val ElementToDecryptOptionKey = "im.vector.app.core.glide.ElementToDecrypt" val ELEMENT_TO_DECRYPT = Option.memory( - ElementToDecryptOptionKey, ElementToDecrypt("", "", "")) + ElementToDecryptOptionKey, ElementToDecrypt("", "", "") +) diff --git a/vector/src/main/java/im/vector/app/core/glide/VectorGlideModelLoader.kt b/vector/src/main/java/im/vector/app/core/glide/VectorGlideModelLoader.kt index 6b42e3fff8..a643ee908c 100644 --- a/vector/src/main/java/im/vector/app/core/glide/VectorGlideModelLoader.kt +++ b/vector/src/main/java/im/vector/app/core/glide/VectorGlideModelLoader.kt @@ -49,7 +49,7 @@ class VectorGlideModelLoaderFactory(private val context: Context) : ModelLoaderF } class VectorGlideModelLoader(private val context: Context) : - ModelLoader { + ModelLoader { override fun handles(model: ImageContentRenderer.Data): Boolean { // Always handle return true @@ -64,7 +64,7 @@ class VectorGlideDataFetcher(context: Context, private val data: ImageContentRenderer.Data, private val width: Int, private val height: Int) : - DataFetcher { + DataFetcher { private val localFilesHelper = LocalFilesHelper(context) private val activeSessionHolder = context.singletonEntryPoint().activeSessionHolder() @@ -121,7 +121,8 @@ class VectorGlideDataFetcher(context: Context, fileName = data.filename, mimeType = data.mimeType, url = data.url, - elementToDecrypt = data.elementToDecrypt) + elementToDecrypt = data.elementToDecrypt + ) } withContext(Dispatchers.Main) { result.fold( diff --git a/vector/src/main/java/im/vector/app/core/hardware/HardwareInfo.kt b/vector/src/main/java/im/vector/app/core/hardware/HardwareInfo.kt index a9aa7be267..8f8e37f9a3 100644 --- a/vector/src/main/java/im/vector/app/core/hardware/HardwareInfo.kt +++ b/vector/src/main/java/im/vector/app/core/hardware/HardwareInfo.kt @@ -29,7 +29,7 @@ class HardwareInfo @Inject constructor( private val context: Context ) { /** - * Tell if the device has a back (or external) camera + * Tell if the device has a back (or external) camera. */ fun hasBackCamera(): Boolean { val manager = context.getSystemService() ?: return Camera.getNumberOfCameras() > 0 diff --git a/vector/src/main/java/im/vector/app/core/intent/ExternalIntentAnalyser.kt b/vector/src/main/java/im/vector/app/core/intent/ExternalIntentData.kt similarity index 97% rename from vector/src/main/java/im/vector/app/core/intent/ExternalIntentAnalyser.kt rename to vector/src/main/java/im/vector/app/core/intent/ExternalIntentData.kt index 0d18f808fb..142a7a6782 100644 --- a/vector/src/main/java/im/vector/app/core/intent/ExternalIntentAnalyser.kt +++ b/vector/src/main/java/im/vector/app/core/intent/ExternalIntentData.kt @@ -23,7 +23,7 @@ import android.net.Uri import androidx.core.util.PatternsCompat.WEB_URL /** - * Inspired from Riot code: RoomMediaMessage.java + * Inspired from Riot code: RoomMediaMessage.java. */ sealed class ExternalIntentData { /** @@ -42,7 +42,7 @@ sealed class ExternalIntentData { ) : ExternalIntentData() /** - * Clip data + * Clip data. */ data class IntentDataClipData( val clipDataItem: ClipData.Item, @@ -50,7 +50,7 @@ sealed class ExternalIntentData { ) : ExternalIntentData() /** - * Constructor from a media Uri/ + * Constructor from a media Uri/. * * @param uri the media uri * @param filename the media file name diff --git a/vector/src/main/java/im/vector/app/core/linkify/VectorAutoLinkPatterns.kt b/vector/src/main/java/im/vector/app/core/linkify/VectorAutoLinkPatterns.kt index bc45ab5676..d98c280f78 100644 --- a/vector/src/main/java/im/vector/app/core/linkify/VectorAutoLinkPatterns.kt +++ b/vector/src/main/java/im/vector/app/core/linkify/VectorAutoLinkPatterns.kt @@ -16,7 +16,7 @@ package im.vector.app.core.linkify /** - * Better support for geo URi + * Better support for geo URI. */ object VectorAutoLinkPatterns { @@ -24,14 +24,16 @@ object VectorAutoLinkPatterns { private const val LAT_OR_LONG_OR_ALT_NUMBER = "-?\\d+(?:\\.\\d+)?" private const val COORDINATE_SYSTEM = ";crs=[\\w-]+" - val GEO_URI: Regex = Regex("(?:geo:)?" + - "(" + LAT_OR_LONG_OR_ALT_NUMBER + ")" + - "," + - "(" + LAT_OR_LONG_OR_ALT_NUMBER + ")" + - "(?:" + "," + LAT_OR_LONG_OR_ALT_NUMBER + ")?" + // altitude - "(?:" + COORDINATE_SYSTEM + ")?" + - "(?:" + ";u=\\d+(?:\\.\\d+)?" + ")?" + // uncertainty in meters - "(?:" + - ";[\\w-]+=(?:[\\w-_.!~*'()]|%[\\da-f][\\da-f])+" + // dafuk - ")*", RegexOption.IGNORE_CASE) + val GEO_URI: Regex = Regex( + "(?:geo:)?" + + "(" + LAT_OR_LONG_OR_ALT_NUMBER + ")" + + "," + + "(" + LAT_OR_LONG_OR_ALT_NUMBER + ")" + + "(?:" + "," + LAT_OR_LONG_OR_ALT_NUMBER + ")?" + // altitude + "(?:" + COORDINATE_SYSTEM + ")?" + + "(?:" + ";u=\\d+(?:\\.\\d+)?" + ")?" + // uncertainty in meters + "(?:" + + ";[\\w-]+=(?:[\\w-_.!~*'()]|%[\\da-f][\\da-f])+" + // dafuk + ")*", RegexOption.IGNORE_CASE + ) } diff --git a/vector/src/main/java/im/vector/app/core/linkify/VectorLinkify.kt b/vector/src/main/java/im/vector/app/core/linkify/VectorLinkify.kt index 0906b398c5..d2184d9bd7 100644 --- a/vector/src/main/java/im/vector/app/core/linkify/VectorLinkify.kt +++ b/vector/src/main/java/im/vector/app/core/linkify/VectorLinkify.kt @@ -22,7 +22,7 @@ import androidx.core.text.util.LinkifyCompat object VectorLinkify { /** - * Better support for auto link than the default implementation + * Better support for auto link than the default implementation. */ fun addLinks(spannable: Spannable, keepExistingUrlSpan: Boolean = false) { // we might want to modify some matches diff --git a/vector/src/main/java/im/vector/app/core/mvrx/ResultExtension.kt b/vector/src/main/java/im/vector/app/core/mvrx/ResultExtension.kt index dfd04ea6f6..4cd68f03ca 100644 --- a/vector/src/main/java/im/vector/app/core/mvrx/ResultExtension.kt +++ b/vector/src/main/java/im/vector/app/core/mvrx/ResultExtension.kt @@ -21,7 +21,7 @@ import com.airbnb.mvrx.Fail import com.airbnb.mvrx.Success /** - * Note: this will be removed when upgrading to mvrx2 + * Note: this will be removed when upgrading to mvrx2. */ suspend fun runCatchingToAsync(block: suspend () -> A): Async { return runCatching { diff --git a/vector/src/main/java/im/vector/app/core/platform/ButtonStateView.kt b/vector/src/main/java/im/vector/app/core/platform/ButtonStateView.kt index e7e91dbfd9..128591fd96 100755 --- a/vector/src/main/java/im/vector/app/core/platform/ButtonStateView.kt +++ b/vector/src/main/java/im/vector/app/core/platform/ButtonStateView.kt @@ -29,7 +29,7 @@ import im.vector.app.core.epoxy.onClick import im.vector.app.databinding.ViewButtonStateBinding class ButtonStateView @JvmOverloads constructor(context: Context, attrs: AttributeSet? = null, defStyle: Int = 0) : - FrameLayout(context, attrs, defStyle) { + FrameLayout(context, attrs, defStyle) { sealed class State { object Button : State() @@ -62,7 +62,8 @@ class ButtonStateView @JvmOverloads constructor(context: Context, attrs: Attribu context.theme.obtainStyledAttributes( attrs, R.styleable.ButtonStateView, - 0, 0) + 0, 0 + ) .apply { try { if (getBoolean(R.styleable.ButtonStateView_bsv_use_flat_button, true)) { diff --git a/vector/src/main/java/im/vector/app/core/platform/MaxHeightScrollView.kt b/vector/src/main/java/im/vector/app/core/platform/MaxHeightScrollView.kt index 8d4c5d8950..da15d4413d 100644 --- a/vector/src/main/java/im/vector/app/core/platform/MaxHeightScrollView.kt +++ b/vector/src/main/java/im/vector/app/core/platform/MaxHeightScrollView.kt @@ -25,7 +25,7 @@ import im.vector.app.R private const val DEFAULT_MAX_HEIGHT = 200 class MaxHeightScrollView @JvmOverloads constructor(context: Context, attrs: AttributeSet? = null, defStyle: Int = 0) : - NestedScrollView(context, attrs, defStyle) { + NestedScrollView(context, attrs, defStyle) { var maxHeight: Int = 0 set(value) { diff --git a/vector/src/main/java/im/vector/app/core/platform/SimpleTextWatcher.kt b/vector/src/main/java/im/vector/app/core/platform/SimpleTextWatcher.kt index 75deebe1ba..0c0f6ef6ec 100644 --- a/vector/src/main/java/im/vector/app/core/platform/SimpleTextWatcher.kt +++ b/vector/src/main/java/im/vector/app/core/platform/SimpleTextWatcher.kt @@ -20,7 +20,7 @@ import android.text.Editable import android.text.TextWatcher /** - * TextWatcher with default no op implementation + * TextWatcher with default no op implementation. */ open class SimpleTextWatcher : TextWatcher { override fun beforeTextChanged(s: CharSequence, start: Int, count: Int, after: Int) { diff --git a/vector/src/main/java/im/vector/app/core/platform/StateView.kt b/vector/src/main/java/im/vector/app/core/platform/StateView.kt index b3d42dc38f..a70bf54aa1 100755 --- a/vector/src/main/java/im/vector/app/core/platform/StateView.kt +++ b/vector/src/main/java/im/vector/app/core/platform/StateView.kt @@ -27,7 +27,7 @@ import im.vector.app.core.extensions.updateConstraintSet import im.vector.app.databinding.ViewStateBinding class StateView @JvmOverloads constructor(context: Context, attrs: AttributeSet? = null, defStyle: Int = 0) : - FrameLayout(context, attrs, defStyle) { + FrameLayout(context, attrs, defStyle) { sealed class State { object Content : State() 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 4796022856..d904a5a4de 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 @@ -46,6 +46,7 @@ import androidx.viewbinding.ViewBinding import com.airbnb.mvrx.MavericksView import com.bumptech.glide.util.Util import com.google.android.material.appbar.MaterialToolbar +import com.google.android.material.dialog.MaterialAlertDialogBuilder import com.google.android.material.snackbar.Snackbar import dagger.hilt.android.EntryPointAccessors import im.vector.app.BuildConfig @@ -86,6 +87,7 @@ import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.onEach import org.matrix.android.sdk.api.extensions.tryOrNull import org.matrix.android.sdk.api.failure.GlobalError +import org.matrix.android.sdk.api.failure.InitialSyncRequestReason import reactivecircus.flowbinding.android.view.clicks import timber.log.Timber import javax.inject.Inject @@ -258,17 +260,37 @@ abstract class VectorBaseActivity : AppCompatActivity(), Maver private fun handleGlobalError(globalError: GlobalError) { when (globalError) { - is GlobalError.InvalidToken -> - handleInvalidToken(globalError) - is GlobalError.ConsentNotGivenError -> - consentNotGivenHelper.displayDialog(globalError.consentUri, - activeSessionHolder.getActiveSession().sessionParams.homeServerHost ?: "") - is GlobalError.CertificateError -> - handleCertificateError(globalError) + is GlobalError.InvalidToken -> handleInvalidToken(globalError) + is GlobalError.ConsentNotGivenError -> displayConsentNotGivenDialog(globalError) + is GlobalError.CertificateError -> handleCertificateError(globalError) GlobalError.ExpiredAccount -> Unit // TODO Handle account expiration + is GlobalError.InitialSyncRequest -> handleInitialSyncRequest(globalError) } } + private fun displayConsentNotGivenDialog(globalError: GlobalError.ConsentNotGivenError) { + consentNotGivenHelper.displayDialog(globalError.consentUri, activeSessionHolder.getActiveSession().sessionParams.homeServerHost ?: "") + } + + private fun handleInitialSyncRequest(initialSyncRequest: GlobalError.InitialSyncRequest) { + MaterialAlertDialogBuilder(this) + .setTitle(R.string.initial_sync_request_title) + .setMessage( + getString( + R.string.initial_sync_request_content, getString( + when (initialSyncRequest.reason) { + InitialSyncRequestReason.IGNORED_USERS_LIST_CHANGE -> R.string.initial_sync_request_reason_unignored_users + } + ) + ) + ) + .setPositiveButton(R.string.ok) { _, _ -> + MainActivity.restartApp(this, MainActivityArgs(clearCache = true)) + } + .setNegativeButton(R.string.later, null) + .show() + } + private fun handleCertificateError(certificateError: GlobalError.CertificateError) { singletonEntryPoint() .unrecognizedCertificateDialog() @@ -298,7 +320,8 @@ abstract class VectorBaseActivity : AppCompatActivity(), Maver mainActivityStarted = true - MainActivity.restartApp(this, + MainActivity.restartApp( + this, MainActivityArgs( clearCredentials = !globalError.softLogout, isUserLoggedOut = true, @@ -354,8 +377,8 @@ abstract class VectorBaseActivity : AppCompatActivity(), Maver private val postResumeScheduledActions = mutableListOf<() -> Unit>() /** - * Schedule action to be done in the next call of onPostResume() - * It fixes bug observed on Android 6 (API 23) + * Schedule action to be done in the next call of onPostResume(). + * It fixes bug observed on Android 6 (API 23). */ protected fun doOnPostResume(action: () -> Unit) { synchronized(postResumeScheduledActions) { @@ -411,7 +434,7 @@ abstract class VectorBaseActivity : AppCompatActivity(), Maver * ========================================================================================== */ /** - * Force to render the activity in fullscreen + * Force to render the activity in fullscreen. */ @Suppress("DEPRECATION") private fun setFullScreen() { @@ -503,7 +526,7 @@ abstract class VectorBaseActivity : AppCompatActivity(), Maver } /** - * Is first creation + * Is first creation. * * @return true if Activity is created for the first time (and not restored by the system) */ @@ -522,7 +545,7 @@ abstract class VectorBaseActivity : AppCompatActivity(), Maver } /** - * Tells if the waiting view is currently displayed + * Tells if the waiting view is currently displayed. * * @return true if the waiting view is displayed */ @@ -539,7 +562,7 @@ abstract class VectorBaseActivity : AppCompatActivity(), Maver } /** - * Hide the waiting view + * Hide the waiting view. */ open fun hideWaitingView() { waitingView?.isVisible = false @@ -567,7 +590,7 @@ abstract class VectorBaseActivity : AppCompatActivity(), Maver open fun getMenuRes() = -1 /** - * Return a object containing other themes for this activity + * Return a object containing other themes for this activity. */ open fun getOtherThemes(): ActivityOtherThemes = ActivityOtherThemes.Default @@ -620,7 +643,7 @@ abstract class VectorBaseActivity : AppCompatActivity(), Maver } /** - * Sets toolbar as actionBar + * Sets toolbar as actionBar. * * @return Instance of [ToolbarConfig] with set of helper methods to configure toolbar * */ diff --git a/vector/src/main/java/im/vector/app/core/platform/VectorBaseFragment.kt b/vector/src/main/java/im/vector/app/core/platform/VectorBaseFragment.kt index 70b265ff9f..38667b774f 100644 --- a/vector/src/main/java/im/vector/app/core/platform/VectorBaseFragment.kt +++ b/vector/src/main/java/im/vector/app/core/platform/VectorBaseFragment.kt @@ -80,11 +80,11 @@ abstract class VectorBaseFragment : Fragment(), MavericksView private var progress: AlertDialog? = null /** - * [ToolbarConfig] instance from host activity + * [ToolbarConfig] instance from host activity. * */ protected var toolbar: ToolbarConfig? = null - get() = (activity as? VectorBaseActivity<*>)?.toolbar - private set + get() = (activity as? VectorBaseActivity<*>)?.toolbar + private set /* ========================================================================================== * View model * ========================================================================================== */ @@ -234,7 +234,7 @@ abstract class VectorBaseFragment : Fragment(), MavericksView * ========================================================================================== */ /** - * Sets toolbar as actionBar for current activity + * Sets toolbar as actionBar for current activity. * * @return Instance of [ToolbarConfig] with set of helper methods to configure toolbar * */ diff --git a/vector/src/main/java/im/vector/app/core/platform/VectorEventViewModel.kt b/vector/src/main/java/im/vector/app/core/platform/VectorEventViewModel.kt index 9ab46557a5..a28fca60d7 100644 --- a/vector/src/main/java/im/vector/app/core/platform/VectorEventViewModel.kt +++ b/vector/src/main/java/im/vector/app/core/platform/VectorEventViewModel.kt @@ -23,7 +23,7 @@ import im.vector.app.core.utils.PublishDataSource interface VectorSharedAction /** - * Parent class to handle navigation events, action events, or other any events + * Parent class to handle navigation events, action events, or other any events. */ open class VectorSharedActionViewModel(private val store: MutableDataSource = PublishDataSource()) : - ViewModel(), MutableDataSource by store + ViewModel(), MutableDataSource by store diff --git a/vector/src/main/java/im/vector/app/core/platform/VectorViewEvents.kt b/vector/src/main/java/im/vector/app/core/platform/VectorViewEvents.kt index 979394a988..61edb97b96 100644 --- a/vector/src/main/java/im/vector/app/core/platform/VectorViewEvents.kt +++ b/vector/src/main/java/im/vector/app/core/platform/VectorViewEvents.kt @@ -17,11 +17,11 @@ package im.vector.app.core.platform /** - * Interface for View Events + * Interface for View Events. */ interface VectorViewEvents /** - * To use when no view events is associated to the ViewModel + * To use when no view events is associated to the ViewModel. */ object EmptyViewEvents : VectorViewEvents diff --git a/vector/src/main/java/im/vector/app/core/platform/VectorViewModelAction.kt b/vector/src/main/java/im/vector/app/core/platform/VectorViewModelAction.kt index a6ce118eb7..24d8bd7310 100644 --- a/vector/src/main/java/im/vector/app/core/platform/VectorViewModelAction.kt +++ b/vector/src/main/java/im/vector/app/core/platform/VectorViewModelAction.kt @@ -19,6 +19,6 @@ package im.vector.app.core.platform interface VectorViewModelAction /** - * To use when no action is associated to the ViewModel + * To use when no action is associated to the ViewModel. */ object EmptyAction : VectorViewModelAction diff --git a/vector/src/main/java/im/vector/app/core/platform/WaitingViewData.kt b/vector/src/main/java/im/vector/app/core/platform/WaitingViewData.kt index e1e9c7e39f..41b569bd14 100644 --- a/vector/src/main/java/im/vector/app/core/platform/WaitingViewData.kt +++ b/vector/src/main/java/im/vector/app/core/platform/WaitingViewData.kt @@ -17,7 +17,7 @@ package im.vector.app.core.platform /** - * Model to display a Waiting View + * Model to display a Waiting View. */ data class WaitingViewData( val message: String, diff --git a/vector/src/main/java/im/vector/app/core/preference/KeywordPreference.kt b/vector/src/main/java/im/vector/app/core/preference/KeywordPreference.kt index b57bb27671..6101f8a597 100644 --- a/vector/src/main/java/im/vector/app/core/preference/KeywordPreference.kt +++ b/vector/src/main/java/im/vector/app/core/preference/KeywordPreference.kt @@ -139,7 +139,7 @@ class KeywordPreference : VectorPreference { keyword.contains("/") -> { context.getString(R.string.settings_notification_keyword_contains_invalid_character, "/") } - else -> null + else -> null } chipTextInputLayout.isErrorEnabled = errorMessage != null diff --git a/vector/src/main/java/im/vector/app/core/preference/PushRulePreference.kt b/vector/src/main/java/im/vector/app/core/preference/PushRulePreference.kt index 78266cf5ee..dad7f26560 100644 --- a/vector/src/main/java/im/vector/app/core/preference/PushRulePreference.kt +++ b/vector/src/main/java/im/vector/app/core/preference/PushRulePreference.kt @@ -53,14 +53,16 @@ class PushRulePreference : VectorPreference { } /** - * Refresh the summary + * Refresh the summary. */ private fun refreshSummary() { - summary = context.getString(when (index) { - NotificationIndex.OFF -> R.string.notification_off - NotificationIndex.SILENT -> R.string.notification_silent - NotificationIndex.NOISY, null -> R.string.notification_noisy - }) + summary = context.getString( + when (index) { + NotificationIndex.OFF -> R.string.notification_off + NotificationIndex.SILENT -> R.string.notification_silent + NotificationIndex.NOISY, null -> R.string.notification_noisy + } + ) } override fun onBindViewHolder(holder: PreferenceViewHolder) { diff --git a/vector/src/main/java/im/vector/app/core/preference/VectorPreference.kt b/vector/src/main/java/im/vector/app/core/preference/VectorPreference.kt index 22fc1758f1..2dd6f058d7 100755 --- a/vector/src/main/java/im/vector/app/core/preference/VectorPreference.kt +++ b/vector/src/main/java/im/vector/app/core/preference/VectorPreference.kt @@ -33,6 +33,7 @@ import androidx.preference.Preference import androidx.preference.PreferenceViewHolder import im.vector.app.R import im.vector.app.features.themes.ThemeUtils +import org.matrix.android.sdk.api.extensions.orFalse import timber.log.Timber /** @@ -152,16 +153,12 @@ open class VectorPreference : Preference { */ private fun addClickListeners(view: View) { view.setOnLongClickListener { - if (null != onPreferenceLongClickListener) { - onPreferenceLongClickListener!!.onPreferenceLongClick(this@VectorPreference) - } else false + onPreferenceLongClickListener?.onPreferenceLongClick(this@VectorPreference).orFalse() } view.setOnClickListener { // call only the click listener - if (onPreferenceClickListener != null) { - onPreferenceClickListener.onPreferenceClick(this@VectorPreference) - } + onPreferenceClickListener?.onPreferenceClick(this@VectorPreference) } } } diff --git a/vector/src/main/java/im/vector/app/core/preference/VectorSwitchPreference.kt b/vector/src/main/java/im/vector/app/core/preference/VectorSwitchPreference.kt index 346e8488b9..635cbff568 100644 --- a/vector/src/main/java/im/vector/app/core/preference/VectorSwitchPreference.kt +++ b/vector/src/main/java/im/vector/app/core/preference/VectorSwitchPreference.kt @@ -30,7 +30,7 @@ import im.vector.app.R import im.vector.app.features.themes.ThemeUtils /** - * Switch preference with title on multiline (only used in XML) + * Switch preference with title on multiline (only used in XML). */ class VectorSwitchPreference : SwitchPreference { diff --git a/vector/src/main/java/im/vector/app/core/pushers/PushersManager.kt b/vector/src/main/java/im/vector/app/core/pushers/PushersManager.kt index 27d6d05708..b1bb4c7d88 100644 --- a/vector/src/main/java/im/vector/app/core/pushers/PushersManager.kt +++ b/vector/src/main/java/im/vector/app/core/pushers/PushersManager.kt @@ -37,7 +37,7 @@ class PushersManager @Inject constructor( suspend fun testPush(pushKey: String) { val currentSession = activeSessionHolder.getActiveSession() - currentSession.testPush( + currentSession.pushersService().testPush( stringProvider.getString(R.string.pusher_http_url), stringProvider.getString(R.string.pusher_app_id), pushKey, @@ -47,12 +47,12 @@ class PushersManager @Inject constructor( fun enqueueRegisterPusherWithFcmKey(pushKey: String): UUID { val currentSession = activeSessionHolder.getActiveSession() - return currentSession.enqueueAddHttpPusher(createHttpPusher(pushKey)) + return currentSession.pushersService().enqueueAddHttpPusher(createHttpPusher(pushKey)) } suspend fun registerPusherWithFcmKey(pushKey: String) { val currentSession = activeSessionHolder.getActiveSession() - currentSession.addHttpPusher(createHttpPusher(pushKey)) + currentSession.pushersService().addHttpPusher(createHttpPusher(pushKey)) } private fun createHttpPusher(pushKey: String) = PushersService.HttpPusher( @@ -70,7 +70,7 @@ class PushersManager @Inject constructor( suspend fun registerEmailForPush(email: String) { val currentSession = activeSessionHolder.getActiveSession() val appName = appNameProvider.getAppName() - currentSession.addEmailPusher( + currentSession.pushersService().addEmailPusher( email = email, lang = localeProvider.current().language, emailBranding = appName, @@ -81,12 +81,12 @@ class PushersManager @Inject constructor( suspend fun unregisterEmailPusher(email: String) { val currentSession = activeSessionHolder.getSafeActiveSession() ?: return - currentSession.removeEmailPusher(email) + currentSession.pushersService().removeEmailPusher(email) } suspend fun unregisterPusher(pushKey: String) { val currentSession = activeSessionHolder.getSafeActiveSession() ?: return - currentSession.removeHttpPusher(pushKey, stringProvider.getString(R.string.pusher_app_id)) + currentSession.pushersService().removeHttpPusher(pushKey, stringProvider.getString(R.string.pusher_app_id)) } companion object { diff --git a/vector/src/main/java/im/vector/app/core/resources/ColorProvider.kt b/vector/src/main/java/im/vector/app/core/resources/ColorProvider.kt index 395b75d336..12c8a47056 100644 --- a/vector/src/main/java/im/vector/app/core/resources/ColorProvider.kt +++ b/vector/src/main/java/im/vector/app/core/resources/ColorProvider.kt @@ -32,7 +32,7 @@ class ColorProvider @Inject constructor(private val context: Context) { } /** - * Translates color attributes to colors + * Translates color attributes to colors. * * @param colorAttribute Color Attribute * @return Requested Color diff --git a/vector/src/main/java/im/vector/app/core/resources/LocaleProvider.kt b/vector/src/main/java/im/vector/app/core/resources/LocaleProvider.kt index fdb5f21b61..2f1b46b555 100644 --- a/vector/src/main/java/im/vector/app/core/resources/LocaleProvider.kt +++ b/vector/src/main/java/im/vector/app/core/resources/LocaleProvider.kt @@ -32,6 +32,6 @@ class LocaleProvider @Inject constructor(private val resources: Resources) { fun LocaleProvider.isEnglishSpeaking() = current().language.startsWith("en") -fun LocaleProvider.getLayoutDirectionFromCurrentLocale() = TextUtils.getLayoutDirectionFromLocale(current()) +fun LocaleProvider.getLayoutDirectionFromCurrentLocale() = TextUtils.getLayoutDirectionFromLocale(current()) fun LocaleProvider.isRTL() = getLayoutDirectionFromCurrentLocale() == View.LAYOUT_DIRECTION_RTL diff --git a/vector/src/main/java/im/vector/app/core/resources/ResourceUtils.kt b/vector/src/main/java/im/vector/app/core/resources/Resource.kt similarity index 100% rename from vector/src/main/java/im/vector/app/core/resources/ResourceUtils.kt rename to vector/src/main/java/im/vector/app/core/resources/Resource.kt diff --git a/vector/src/main/java/im/vector/app/core/resources/UserPreferencesProvider.kt b/vector/src/main/java/im/vector/app/core/resources/UserPreferencesProvider.kt index 3aa1964d8d..d39dcbe318 100644 --- a/vector/src/main/java/im/vector/app/core/resources/UserPreferencesProvider.kt +++ b/vector/src/main/java/im/vector/app/core/resources/UserPreferencesProvider.kt @@ -52,4 +52,8 @@ class UserPreferencesProvider @Inject constructor(private val vectorPreferences: fun areThreadMessagesEnabled(): Boolean { return vectorPreferences.areThreadMessagesEnabled() } + + fun showLiveSenderInfo(): Boolean { + return vectorPreferences.showLiveSenderInfo() + } } diff --git a/vector/src/main/java/im/vector/app/core/services/BluetoothHeadsetReceiver.kt b/vector/src/main/java/im/vector/app/core/services/BluetoothHeadsetReceiver.kt index 3f2cd2566e..d050890509 100644 --- a/vector/src/main/java/im/vector/app/core/services/BluetoothHeadsetReceiver.kt +++ b/vector/src/main/java/im/vector/app/core/services/BluetoothHeadsetReceiver.kt @@ -37,9 +37,10 @@ class BluetoothHeadsetReceiver : BroadcastReceiver() { val plugged: Boolean, val headsetName: String?, /** - * BluetoothClass.Device.AUDIO_VIDEO_HANDSFREE - * BluetoothClass.Device.AUDIO_VIDEO_CAR_AUDIO - * AUDIO_VIDEO_WEARABLE_HEADSET + * Can be: + * - BluetoothClass.Device.AUDIO_VIDEO_HANDSFREE + * - BluetoothClass.Device.AUDIO_VIDEO_CAR_AUDIO + * - AUDIO_VIDEO_WEARABLE_HEADSET. */ val deviceClass: Int ) diff --git a/vector/src/main/java/im/vector/app/core/services/CallRingPlayer.kt b/vector/src/main/java/im/vector/app/core/services/CallRingPlayer.kt index b2d9382aae..702092679f 100644 --- a/vector/src/main/java/im/vector/app/core/services/CallRingPlayer.kt +++ b/vector/src/main/java/im/vector/app/core/services/CallRingPlayer.kt @@ -140,13 +140,15 @@ class CallRingPlayerOutgoing( mediaPlayer.setOnErrorListener(MediaPlayerErrorListener()) mediaPlayer.isLooping = true if (Build.VERSION.SDK_INT > Build.VERSION_CODES.LOLLIPOP) { - mediaPlayer.setAudioAttributes(AudioAttributes.Builder() - .setContentType(AudioAttributes.CONTENT_TYPE_SPEECH) - .setUsage(AudioAttributes.USAGE_VOICE_COMMUNICATION) - // TODO Change to ? - // .setContentType(AudioAttributes.CONTENT_TYPE_UNKNOWN) - // .setUsage(AudioAttributes.USAGE_NOTIFICATION_RINGTONE) - .build()) + mediaPlayer.setAudioAttributes( + AudioAttributes.Builder() + .setContentType(AudioAttributes.CONTENT_TYPE_SPEECH) + .setUsage(AudioAttributes.USAGE_VOICE_COMMUNICATION) + // TODO Change to ? + // .setContentType(AudioAttributes.CONTENT_TYPE_UNKNOWN) + // .setUsage(AudioAttributes.USAGE_NOTIFICATION_RINGTONE) + .build() + ) } else { @Suppress("DEPRECATION") mediaPlayer.setAudioStreamType(AudioManager.STREAM_RING) diff --git a/vector/src/main/java/im/vector/app/core/services/CallService.kt b/vector/src/main/java/im/vector/app/core/services/CallService.kt index 4dd95fd49a..df9e670423 100644 --- a/vector/src/main/java/im/vector/app/core/services/CallService.kt +++ b/vector/src/main/java/im/vector/app/core/services/CallService.kt @@ -48,7 +48,7 @@ import javax.inject.Inject private val loggerTag = LoggerTag("CallService", LoggerTag.VOIP) /** - * Foreground service to manage calls + * Foreground service to manage calls. */ @AndroidEntryPoint class CallService : VectorService() { diff --git a/vector/src/main/java/im/vector/app/core/services/VectorService.kt b/vector/src/main/java/im/vector/app/core/services/VectorService.kt index 888f7a8cac..cc816c21a1 100644 --- a/vector/src/main/java/im/vector/app/core/services/VectorService.kt +++ b/vector/src/main/java/im/vector/app/core/services/VectorService.kt @@ -22,7 +22,7 @@ import android.os.IBinder import timber.log.Timber /** - * Parent class for all services + * Parent class for all services. */ abstract class VectorService : Service() { diff --git a/vector/src/main/java/im/vector/app/core/services/VectorSyncService.kt b/vector/src/main/java/im/vector/app/core/services/VectorSyncService.kt index 5dbea8dcc4..131276bea6 100644 --- a/vector/src/main/java/im/vector/app/core/services/VectorSyncService.kt +++ b/vector/src/main/java/im/vector/app/core/services/VectorSyncService.kt @@ -33,6 +33,8 @@ import androidx.work.WorkerParameters import dagger.hilt.android.AndroidEntryPoint import im.vector.app.R import im.vector.app.core.platform.PendingIntentCompat +import im.vector.app.core.time.Clock +import im.vector.app.core.time.DefaultClock import im.vector.app.features.notifications.NotificationUtils import im.vector.app.features.settings.BackgroundSyncMode import org.matrix.android.sdk.api.Matrix @@ -77,6 +79,7 @@ class VectorSyncService : SyncService() { @Inject lateinit var notificationUtils: NotificationUtils @Inject lateinit var matrix: Matrix + @Inject lateinit var clock: Clock override fun provideMatrix() = matrix @@ -102,7 +105,8 @@ class VectorSyncService : SyncService() { syncTimeoutSeconds = syncTimeoutSeconds, syncDelaySeconds = syncDelaySeconds, isPeriodic = true, - isNetworkBack = false + isNetworkBack = false, + currentTimeMillis = clock.epochMillis() ) } @@ -114,9 +118,10 @@ class VectorSyncService : SyncService() { val rescheduleSyncWorkRequest: WorkRequest = OneTimeWorkRequestBuilder() .setInputData(RestartWhenNetworkOn.createInputData(sessionId, syncTimeoutSeconds, syncDelaySeconds, isPeriodic)) - .setConstraints(Constraints.Builder() - .setRequiredNetworkType(NetworkType.CONNECTED) - .build() + .setConstraints( + Constraints.Builder() + .setRequiredNetworkType(NetworkType.CONNECTED) + .build() ) .build() @@ -137,20 +142,27 @@ class VectorSyncService : SyncService() { } // I do not move or rename this class, since I'm not sure about the side effect regarding the WorkManager - class RestartWhenNetworkOn(appContext: Context, workerParams: WorkerParameters) : - Worker(appContext, workerParams) { + class RestartWhenNetworkOn( + appContext: Context, + workerParams: WorkerParameters + ) : Worker(appContext, workerParams) { + override fun doWork(): Result { Timber.d("## Sync: RestartWhenNetworkOn.doWork()") val sessionId = inputData.getString(KEY_SESSION_ID) ?: return Result.failure() val syncTimeoutSeconds = inputData.getInt(KEY_SYNC_TIMEOUT_SECONDS, BackgroundSyncMode.DEFAULT_SYNC_TIMEOUT_SECONDS) val syncDelaySeconds = inputData.getInt(KEY_SYNC_DELAY_SECONDS, BackgroundSyncMode.DEFAULT_SYNC_DELAY_SECONDS) val isPeriodic = inputData.getBoolean(KEY_IS_PERIODIC, false) + + // Not sure how to inject a Clock here + val clock = DefaultClock() applicationContext.rescheduleSyncService( sessionId = sessionId, syncTimeoutSeconds = syncTimeoutSeconds, syncDelaySeconds = syncDelaySeconds, isPeriodic = isPeriodic, - isNetworkBack = true + isNetworkBack = true, + currentTimeMillis = clock.epochMillis() ) // Indicate whether the work finished successfully with the Result return Result.success() @@ -182,7 +194,8 @@ private fun Context.rescheduleSyncService(sessionId: String, syncTimeoutSeconds: Int, syncDelaySeconds: Int, isPeriodic: Boolean, - isNetworkBack: Boolean) { + isNetworkBack: Boolean, + currentTimeMillis: Long) { Timber.d("## Sync: rescheduleSyncService") val intent = if (isPeriodic) { VectorSyncService.newPeriodicIntent( @@ -208,7 +221,7 @@ private fun Context.rescheduleSyncService(sessionId: String, } else { PendingIntent.getService(this, 0, intent, PendingIntentCompat.FLAG_IMMUTABLE) } - val firstMillis = System.currentTimeMillis() + syncDelaySeconds * 1000L + val firstMillis = currentTimeMillis + syncDelaySeconds * 1000L val alarmMgr = getSystemService()!! if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { alarmMgr.setAndAllowWhileIdle(AlarmManager.RTC_WAKEUP, firstMillis, pendingIntent) diff --git a/vector/src/main/java/im/vector/app/core/services/WiredHeadsetStateReceiver.kt b/vector/src/main/java/im/vector/app/core/services/WiredHeadsetStateReceiver.kt index ac6ced002e..0d3ebafb04 100644 --- a/vector/src/main/java/im/vector/app/core/services/WiredHeadsetStateReceiver.kt +++ b/vector/src/main/java/im/vector/app/core/services/WiredHeadsetStateReceiver.kt @@ -25,7 +25,7 @@ import timber.log.Timber import java.lang.ref.WeakReference /** - * Dynamic broadcast receiver to detect headset plug/unplug + * Dynamic broadcast receiver to detect headset plug/unplug. */ class WiredHeadsetStateReceiver : BroadcastReceiver() { diff --git a/vector/src/main/java/im/vector/app/core/ui/bottomsheet/BottomSheetGeneric.kt b/vector/src/main/java/im/vector/app/core/ui/bottomsheet/BottomSheetGeneric.kt index bd7a07c640..9f864aaa68 100644 --- a/vector/src/main/java/im/vector/app/core/ui/bottomsheet/BottomSheetGeneric.kt +++ b/vector/src/main/java/im/vector/app/core/ui/bottomsheet/BottomSheetGeneric.kt @@ -29,7 +29,7 @@ import im.vector.app.databinding.BottomSheetGenericListBinding import javax.inject.Inject /** - * Generic Bottom sheet with actions + * Generic Bottom sheet with actions. */ abstract class BottomSheetGeneric : VectorBaseBottomSheetDialogFragment(), diff --git a/vector/src/main/java/im/vector/app/core/ui/bottomsheet/BottomSheetGenericController.kt b/vector/src/main/java/im/vector/app/core/ui/bottomsheet/BottomSheetGenericController.kt index 6e92549809..31d13c760e 100644 --- a/vector/src/main/java/im/vector/app/core/ui/bottomsheet/BottomSheetGenericController.kt +++ b/vector/src/main/java/im/vector/app/core/ui/bottomsheet/BottomSheetGenericController.kt @@ -18,10 +18,10 @@ package im.vector.app.core.ui.bottomsheet import com.airbnb.epoxy.TypedEpoxyController /** - * Epoxy controller for generic bottom sheet actions + * Epoxy controller for generic bottom sheet actions. */ abstract class BottomSheetGenericController : - TypedEpoxyController() { + TypedEpoxyController() { var listener: Listener? = null diff --git a/vector/src/main/java/im/vector/app/core/ui/bottomsheet/BottomSheetGenericRadioAction.kt b/vector/src/main/java/im/vector/app/core/ui/bottomsheet/BottomSheetGenericRadioAction.kt index 88eaf587a8..dc298c434f 100644 --- a/vector/src/main/java/im/vector/app/core/ui/bottomsheet/BottomSheetGenericRadioAction.kt +++ b/vector/src/main/java/im/vector/app/core/ui/bottomsheet/BottomSheetGenericRadioAction.kt @@ -20,7 +20,7 @@ import im.vector.app.core.epoxy.bottomsheet.BottomSheetRadioActionItem_ import im.vector.app.core.platform.VectorSharedAction /** - * Parent class for a bottom sheet action + * Parent class for a bottom sheet action. */ open class BottomSheetGenericRadioAction( open val title: String?, diff --git a/vector/src/main/java/im/vector/app/core/ui/bottomsheet/BottomSheetGenericSharedActionViewModel.kt b/vector/src/main/java/im/vector/app/core/ui/bottomsheet/BottomSheetGenericSharedActionViewModel.kt index 49147b954a..2443784df9 100644 --- a/vector/src/main/java/im/vector/app/core/ui/bottomsheet/BottomSheetGenericSharedActionViewModel.kt +++ b/vector/src/main/java/im/vector/app/core/ui/bottomsheet/BottomSheetGenericSharedActionViewModel.kt @@ -20,6 +20,6 @@ import im.vector.app.core.platform.VectorSharedAction import im.vector.app.core.platform.VectorSharedActionViewModel /** - * Activity shared view model to handle bottom sheet quick actions + * Activity shared view model to handle bottom sheet quick actions. */ abstract class BottomSheetGenericSharedActionViewModel : VectorSharedActionViewModel() diff --git a/vector/src/main/java/im/vector/app/core/ui/list/GenericEmptyWithActionItem.kt b/vector/src/main/java/im/vector/app/core/ui/list/GenericEmptyWithActionItem.kt index 5801ca6b7c..4cc39af0a5 100644 --- a/vector/src/main/java/im/vector/app/core/ui/list/GenericEmptyWithActionItem.kt +++ b/vector/src/main/java/im/vector/app/core/ui/list/GenericEmptyWithActionItem.kt @@ -33,7 +33,7 @@ import im.vector.app.core.epoxy.onClick import im.vector.app.core.extensions.setTextOrHide /** - * A generic list item to display when there is no results, with an optional CTA + * A generic list item to display when there is no results, with an optional CTA. */ @EpoxyModelClass(layout = R.layout.item_generic_empty_state) abstract class GenericEmptyWithActionItem : VectorEpoxyModel() { diff --git a/vector/src/main/java/im/vector/app/core/ui/list/GenericPillItem.kt b/vector/src/main/java/im/vector/app/core/ui/list/GenericPillItem.kt index 09fdcded6e..a51c1b70ed 100644 --- a/vector/src/main/java/im/vector/app/core/ui/list/GenericPillItem.kt +++ b/vector/src/main/java/im/vector/app/core/ui/list/GenericPillItem.kt @@ -34,7 +34,7 @@ import im.vector.app.features.themes.ThemeUtils import im.vector.lib.core.utils.epoxy.charsequence.EpoxyCharSequence /** - * A generic list item with a rounded corner background and an optional icon + * A generic list item with a rounded corner background and an optional icon. */ @EpoxyModelClass(layout = R.layout.item_generic_pill_footer) abstract class GenericPillItem : VectorEpoxyModel() { diff --git a/vector/src/main/java/im/vector/app/core/ui/views/CompatKonfetti.kt b/vector/src/main/java/im/vector/app/core/ui/views/CompatKonfetti.kt new file mode 100644 index 0000000000..6b969fe2a5 --- /dev/null +++ b/vector/src/main/java/im/vector/app/core/ui/views/CompatKonfetti.kt @@ -0,0 +1,44 @@ +/* + * 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.ui.views + +import android.content.Context +import android.os.Build +import android.util.AttributeSet +import android.view.View +import nl.dionsegijn.konfetti.xml.KonfettiView + +/** + * Konfetti workaround to avoid crashes on API 21/22 + * https://github.com/DanielMartinus/Konfetti/issues/297 + */ +class CompatKonfetti @JvmOverloads constructor( + context: Context, + attrs: AttributeSet? = null +) : KonfettiView(context, attrs) { + + override fun onVisibilityChanged(changedView: View, visibility: Int) { + when (Build.VERSION.SDK_INT) { + Build.VERSION_CODES.LOLLIPOP, Build.VERSION_CODES.LOLLIPOP_MR1 -> safeOnVisibilityChanged(changedView, visibility) + else -> super.onVisibilityChanged(changedView, visibility) + } + } + + private fun safeOnVisibilityChanged(changedView: View, visibility: Int) { + runCatching { super.onVisibilityChanged(changedView, visibility) } + } +} diff --git a/vector/src/main/java/im/vector/app/core/ui/views/KeysBackupBanner.kt b/vector/src/main/java/im/vector/app/core/ui/views/KeysBackupBanner.kt index 58a5666e94..f2ea79984e 100755 --- a/vector/src/main/java/im/vector/app/core/ui/views/KeysBackupBanner.kt +++ b/vector/src/main/java/im/vector/app/core/ui/views/KeysBackupBanner.kt @@ -28,8 +28,8 @@ import im.vector.app.databinding.ViewKeysBackupBannerBinding import timber.log.Timber /** - * The view used in VectorHomeActivity to show some information about the keys backup state - * It does have a unique render method + * The view used in VectorHomeActivity to show some information about the keys backup state. + * It does have a unique render method. */ class KeysBackupBanner @JvmOverloads constructor( context: Context, @@ -51,7 +51,7 @@ class KeysBackupBanner @JvmOverloads constructor( } /** - * This methods is responsible for rendering the view according to the newState + * This methods is responsible for rendering the view according to the newState. * * @param newState the newState representing the view */ @@ -182,7 +182,7 @@ class KeysBackupBanner @JvmOverloads constructor( } /** - * Hide all views that are not visible in all state + * Hide all views that are not visible in all state. */ private fun hideAll() { views.viewKeysBackupBannerText2.isVisible = false @@ -191,8 +191,8 @@ class KeysBackupBanner @JvmOverloads constructor( } /** - * The state representing the view - * It can take one state at a time + * The state representing the view. + * It can take one state at a time. */ sealed class State { // Not yet rendered @@ -215,7 +215,7 @@ class KeysBackupBanner @JvmOverloads constructor( } /** - * An interface to delegate some actions to another object + * An interface to delegate some actions to another object. */ interface Delegate { fun setupKeysBackup() @@ -239,7 +239,7 @@ class KeysBackupBanner @JvmOverloads constructor( private const val BANNER_UPDATE_DO_NOT_SHOW_FOR_VERSION = "BANNER_UPDATE_DO_NOT_SHOW_FOR_VERSION" /** - * Inform the banner that a Recover has been done for this version, so do not show the Recover banner for this version + * Inform the banner that a Recover has been done for this version, so do not show the Recover banner for this version. */ fun onRecoverDoneForVersion(context: Context, version: String) { DefaultSharedPreferences.getInstance(context).edit { diff --git a/vector/src/main/java/im/vector/app/core/ui/views/NotificationAreaView.kt b/vector/src/main/java/im/vector/app/core/ui/views/NotificationAreaView.kt index 5190bb21a8..d9624f65e9 100644 --- a/vector/src/main/java/im/vector/app/core/ui/views/NotificationAreaView.kt +++ b/vector/src/main/java/im/vector/app/core/ui/views/NotificationAreaView.kt @@ -37,8 +37,8 @@ import org.matrix.android.sdk.api.session.events.model.Event import timber.log.Timber /** - * The view used to show some information about the room - * It does have a unique render method + * The view used to show some information about the room. + * It does have a unique render method. */ class NotificationAreaView @JvmOverloads constructor( context: Context, @@ -56,7 +56,7 @@ class NotificationAreaView @JvmOverloads constructor( } /** - * This methods is responsible for rendering the view according to the newState + * This methods is responsible for rendering the view according to the newState. * * @param newState the newState representing the view */ @@ -169,8 +169,8 @@ class NotificationAreaView @JvmOverloads constructor( } /** - * The state representing the view - * It can take one state at a time + * The state representing the view. + * It can take one state at a time. */ sealed class State { @@ -195,7 +195,7 @@ class NotificationAreaView @JvmOverloads constructor( } /** - * An interface to delegate some actions to another object + * An interface to delegate some actions to another object. */ interface Delegate { fun onTombstoneEventClicked() diff --git a/vector/src/main/java/im/vector/app/core/ui/views/PasswordStrengthBar.kt b/vector/src/main/java/im/vector/app/core/ui/views/PasswordStrengthBar.kt index a984707bf7..04bc821387 100644 --- a/vector/src/main/java/im/vector/app/core/ui/views/PasswordStrengthBar.kt +++ b/vector/src/main/java/im/vector/app/core/ui/views/PasswordStrengthBar.kt @@ -24,8 +24,8 @@ import im.vector.app.R import im.vector.app.databinding.ViewPasswordStrengthBarBinding /** - * A password strength bar custom widget - * Strength is an Integer + * A password strength bar custom widget. + * Strength is an Integer * -> 0 No strength * -> 1 Weak * -> 2 Fair @@ -36,7 +36,7 @@ class PasswordStrengthBar @JvmOverloads constructor( context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0) : - LinearLayout(context, attrs, defStyleAttr) { + LinearLayout(context, attrs, defStyleAttr) { private val views: ViewPasswordStrengthBarBinding diff --git a/vector/src/main/java/im/vector/app/core/ui/views/PresenceStateImageView.kt b/vector/src/main/java/im/vector/app/core/ui/views/PresenceStateImageView.kt index 82675e8c11..a1d3e49f3b 100644 --- a/vector/src/main/java/im/vector/app/core/ui/views/PresenceStateImageView.kt +++ b/vector/src/main/java/im/vector/app/core/ui/views/PresenceStateImageView.kt @@ -25,7 +25,7 @@ import org.matrix.android.sdk.api.session.presence.model.PresenceEnum import org.matrix.android.sdk.api.session.presence.model.UserPresence /** - * Custom ImageView to dynamically render Presence state in multiple screens + * Custom ImageView to dynamically render Presence state in multiple screens. */ class PresenceStateImageView @JvmOverloads constructor( context: Context, @@ -42,13 +42,17 @@ class PresenceStateImageView @JvmOverloads constructor( contentDescription = context.getString(R.string.a11y_presence_online) } PresenceEnum.UNAVAILABLE -> { - setImageResource(R.drawable.ic_presence_offline) + setImageResource(R.drawable.ic_presence_away) contentDescription = context.getString(R.string.a11y_presence_unavailable) } PresenceEnum.OFFLINE -> { setImageResource(R.drawable.ic_presence_offline) contentDescription = context.getString(R.string.a11y_presence_offline) } + PresenceEnum.BUSY -> { + setImageResource(R.drawable.ic_presence_busy) + contentDescription = context.getString(R.string.a11y_presence_busy) + } null -> Unit } } diff --git a/vector/src/main/java/im/vector/app/core/ui/views/ReadReceiptsView.kt b/vector/src/main/java/im/vector/app/core/ui/views/ReadReceiptsView.kt index 3a79e7e328..8477eddeea 100644 --- a/vector/src/main/java/im/vector/app/core/ui/views/ReadReceiptsView.kt +++ b/vector/src/main/java/im/vector/app/core/ui/views/ReadReceiptsView.kt @@ -107,11 +107,13 @@ class ReadReceiptsView @JvmOverloads constructor( else -> if (displayNames.size >= 2) { val qty = readReceipts.size - 2 - context.resources.getQuantityString(R.plurals.two_and_some_others_read, + context.resources.getQuantityString( + R.plurals.two_and_some_others_read, qty, displayNames[0], displayNames[1], - qty) + qty + ) } else { context.resources.getQuantityString(R.plurals.fallback_users_read, readReceipts.size) } diff --git a/vector/src/main/java/im/vector/app/core/ui/views/TypingMessageAvatar.kt b/vector/src/main/java/im/vector/app/core/ui/views/TypingMessageAvatar.kt index 22312f36fa..1f78584a6b 100644 --- a/vector/src/main/java/im/vector/app/core/ui/views/TypingMessageAvatar.kt +++ b/vector/src/main/java/im/vector/app/core/ui/views/TypingMessageAvatar.kt @@ -22,7 +22,7 @@ import android.view.View import android.view.ViewGroup import android.widget.ImageView import android.widget.LinearLayout -import im.vector.app.core.utils.DimensionConverter +import im.vector.app.R import im.vector.app.features.home.AvatarRenderer import org.matrix.android.sdk.api.session.room.sender.SenderInfo import org.matrix.android.sdk.api.util.toMatrixItem @@ -34,19 +34,22 @@ class TypingMessageAvatar @JvmOverloads constructor( ) : LinearLayout(context, attrs, defStyleAttr) { companion object { - const val AVATAR_SIZE_DP = 20 const val OVERLAP_FACT0R = -3 // =~ 30% to left } + private val typingAvatarSize by lazy(LazyThreadSafetyMode.NONE) { + context.resources.getDimension(R.dimen.typing_avatar_size).toInt() + } + fun render(typingUsers: List, avatarRenderer: AvatarRenderer) { removeAllViews() for ((index, value) in typingUsers.withIndex()) { val avatar = ImageView(context) avatar.id = View.generateViewId() val layoutParams = MarginLayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT) - if (index != 0) layoutParams.marginStart = DimensionConverter(resources).dpToPx(AVATAR_SIZE_DP / OVERLAP_FACT0R) - layoutParams.width = DimensionConverter(resources).dpToPx(AVATAR_SIZE_DP) - layoutParams.height = DimensionConverter(resources).dpToPx(AVATAR_SIZE_DP) + if (index != 0) layoutParams.marginStart = typingAvatarSize / OVERLAP_FACT0R + layoutParams.width = typingAvatarSize + layoutParams.height = typingAvatarSize avatar.layoutParams = layoutParams avatarRenderer.render(value.toMatrixItem(), avatar) addView(avatar) diff --git a/vector/src/main/java/im/vector/app/core/ui/views/TypingMessageDotsView.kt b/vector/src/main/java/im/vector/app/core/ui/views/TypingMessageDotsView.kt index bc06254b0d..2e9945c8d7 100644 --- a/vector/src/main/java/im/vector/app/core/ui/views/TypingMessageDotsView.kt +++ b/vector/src/main/java/im/vector/app/core/ui/views/TypingMessageDotsView.kt @@ -30,7 +30,7 @@ import androidx.core.view.setMargins import im.vector.app.R class TypingMessageDotsView(context: Context, attrs: AttributeSet) : - LinearLayout(context, attrs) { + LinearLayout(context, attrs) { companion object { const val DEFAULT_CIRCLE_DURATION = 1000L diff --git a/vector/src/main/java/im/vector/app/core/ui/views/TypingMessageView.kt b/vector/src/main/java/im/vector/app/core/ui/views/TypingMessageView.kt index 2dc9eedd99..263f043fad 100644 --- a/vector/src/main/java/im/vector/app/core/ui/views/TypingMessageView.kt +++ b/vector/src/main/java/im/vector/app/core/ui/views/TypingMessageView.kt @@ -31,7 +31,8 @@ import javax.inject.Inject class TypingMessageView @JvmOverloads constructor( context: Context, attrs: AttributeSet? = null, - defStyleAttr: Int = 0) : ConstraintLayout(context, attrs, defStyleAttr) { + defStyleAttr: Int = 0 +) : ConstraintLayout(context, attrs, defStyleAttr) { val views: TypingMessageLayoutBinding @@ -44,8 +45,8 @@ class TypingMessageView @JvmOverloads constructor( } fun render(typingUsers: List, avatarRenderer: AvatarRenderer) { - views.usersName.text = typingHelper.getNotificationTypingMessage(typingUsers) - views.avatars.render(typingUsers, avatarRenderer) + views.typingUserText.text = typingHelper.getNotificationTypingMessage(typingUsers) + views.typingUserAvatars.render(typingUsers, avatarRenderer) } override fun onDetachedFromWindow() { diff --git a/vector/src/main/java/im/vector/app/core/utils/AssetReader.kt b/vector/src/main/java/im/vector/app/core/utils/AssetReader.kt index 41fca42cb3..9de98e611e 100644 --- a/vector/src/main/java/im/vector/app/core/utils/AssetReader.kt +++ b/vector/src/main/java/im/vector/app/core/utils/AssetReader.kt @@ -21,7 +21,7 @@ import timber.log.Timber import javax.inject.Inject /** - * Read asset files + * Read asset files. */ class AssetReader @Inject constructor(private val context: Context) { diff --git a/vector/src/main/java/im/vector/app/core/utils/DebouncedClickListener.kt b/vector/src/main/java/im/vector/app/core/utils/DebouncedClickListener.kt index f7c3eec112..319c7e65f5 100644 --- a/vector/src/main/java/im/vector/app/core/utils/DebouncedClickListener.kt +++ b/vector/src/main/java/im/vector/app/core/utils/DebouncedClickListener.kt @@ -21,8 +21,8 @@ import timber.log.Timber import java.util.WeakHashMap /** - * Simple Debounced OnClickListener - * Safe to use in different views + * Simple Debounced OnClickListener. + * Safe to use in different views. */ class DebouncedClickListener( val original: View.OnClickListener, diff --git a/vector/src/main/java/im/vector/app/core/utils/Emoji.kt b/vector/src/main/java/im/vector/app/core/utils/Emoji.kt index d73af1e917..d6a63dca10 100644 --- a/vector/src/main/java/im/vector/app/core/utils/Emoji.kt +++ b/vector/src/main/java/im/vector/app/core/utils/Emoji.kt @@ -32,7 +32,7 @@ fun containsOnlyEmojis(str: String?): Boolean { } /** - * Same as split, but considering emojis + * Same as split, but considering emojis. */ fun CharSequence.splitEmoji(): List { val result = mutableListOf() diff --git a/vector/src/main/java/im/vector/app/core/utils/ExternalApplicationsUtil.kt b/vector/src/main/java/im/vector/app/core/utils/ExternalApplicationsUtil.kt index a9375b6545..8bfbcaeb92 100644 --- a/vector/src/main/java/im/vector/app/core/utils/ExternalApplicationsUtil.kt +++ b/vector/src/main/java/im/vector/app/core/utils/ExternalApplicationsUtil.kt @@ -62,7 +62,7 @@ import java.util.Date import java.util.Locale /** - * Open a url in the internet browser of the system + * Open a url in the internet browser of the system. */ fun openUrlInExternalBrowser(context: Context, url: String?) { url?.let { @@ -71,7 +71,7 @@ fun openUrlInExternalBrowser(context: Context, url: String?) { } /** - * Open a uri in the internet browser of the system + * Open a uri in the internet browser of the system. */ fun openUrlInExternalBrowser(context: Context, uri: Uri?) { uri?.let { @@ -85,9 +85,9 @@ fun openUrlInExternalBrowser(context: Context, uri: Uri?) { } /** - * Open url in custom tab or, if not available, in the default browser + * Open url in custom tab or, if not available, in the default browser. * If several compatible browsers are installed, the user will be proposed to choose one. - * Ref: https://developer.chrome.com/multidevice/android/customtabs + * Ref: https://developer.chrome.com/multidevice/android/customtabs. */ fun openUrlInChromeCustomTab(context: Context, session: CustomTabsSession?, @@ -120,7 +120,7 @@ fun openUrlInChromeCustomTab(context: Context, } /** - * Open file selection activity + * Open file selection activity. */ fun openFileSelection(activity: Activity, activityResultLauncher: ActivityResultLauncher?, @@ -144,11 +144,14 @@ fun openFileSelection(activity: Activity, } /** - * Send an email to address with optional subject and message + * Send an email to address with optional subject and message. */ fun sendMailTo(address: String, subject: String? = null, message: String? = null, activity: Activity) { - val intent = Intent(Intent.ACTION_SENDTO, Uri.fromParts( - "mailto", address, null)) + val intent = Intent( + Intent.ACTION_SENDTO, Uri.fromParts( + "mailto", address, null + ) + ) intent.putExtra(Intent.EXTRA_SUBJECT, subject) intent.putExtra(Intent.EXTRA_TEXT, message) @@ -156,7 +159,7 @@ fun sendMailTo(address: String, subject: String? = null, message: String? = null } /** - * Open an arbitrary uri + * Open an arbitrary uri. */ fun openUri(activity: Activity, uri: String) { val intent = Intent(Intent.ACTION_VIEW, Uri.parse(uri)) @@ -184,7 +187,7 @@ fun openMedia(activity: Activity, savedMediaPath: String, mimeType: String) { } /** - * Open external location + * Open external location. * @param activity the activity * @param latitude latitude of the location * @param longitude longitude of the location @@ -248,7 +251,12 @@ private fun appendTimeToFilename(name: String): String { return """${filename}_$dateExtension.$fileExtension""" } -suspend fun saveMedia(context: Context, file: File, title: String, mediaMimeType: String?, notificationUtils: NotificationUtils) { +suspend fun saveMedia(context: Context, + file: File, + title: String, + mediaMimeType: String?, + notificationUtils: NotificationUtils, + currentTimeMillis: Long) { withContext(Dispatchers.IO) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { val filename = appendTimeToFilename(title) @@ -257,8 +265,8 @@ suspend fun saveMedia(context: Context, file: File, title: String, mediaMimeType put(MediaStore.Images.Media.TITLE, filename) put(MediaStore.Images.Media.DISPLAY_NAME, filename) put(MediaStore.Images.Media.MIME_TYPE, mediaMimeType) - put(MediaStore.Images.Media.DATE_ADDED, System.currentTimeMillis()) - put(MediaStore.Images.Media.DATE_TAKEN, System.currentTimeMillis()) + put(MediaStore.Images.Media.DATE_ADDED, currentTimeMillis) + put(MediaStore.Images.Media.DATE_TAKEN, currentTimeMillis) } val externalContentUri = when { mediaMimeType?.isMimeTypeImage() == true -> MediaStore.Images.Media.EXTERNAL_CONTENT_URI @@ -289,7 +297,7 @@ suspend fun saveMedia(context: Context, file: File, title: String, mediaMimeType } } } else { - saveMediaLegacy(context, mediaMimeType, title, file) + saveMediaLegacy(context, mediaMimeType, title, file, currentTimeMillis) } } } @@ -298,7 +306,8 @@ suspend fun saveMedia(context: Context, file: File, title: String, mediaMimeType private fun saveMediaLegacy(context: Context, mediaMimeType: String?, title: String, - file: File) { + file: File, + currentTimeMillis: Long) { val state = Environment.getExternalStorageState() if (Environment.MEDIA_MOUNTED != state) { context.toast(context.getString(R.string.error_saving_media_file)) @@ -319,7 +328,7 @@ private fun saveMediaLegacy(context: Context, } else { title } - val savedFile = saveFileIntoLegacy(file, downloadDir, outputFilename) + val savedFile = saveFileIntoLegacy(file, downloadDir, outputFilename, currentTimeMillis) if (savedFile != null) { val downloadManager = context.getSystemService() downloadManager?.addCompletedDownload( @@ -329,7 +338,8 @@ private fun saveMediaLegacy(context: Context, mediaMimeType ?: MimeTypes.OctetStream, savedFile.absolutePath, savedFile.length(), - true) + true + ) addToGallery(savedFile, mediaMimeType, context) } } catch (error: Throwable) { @@ -354,7 +364,7 @@ private fun addToGallery(savedFile: File, mediaMimeType: String?, context: Conte } /** - * Open the play store to the provided application Id, default to this app + * Open the play store to the provided application Id, default to this app. */ fun openPlayStore(activity: Activity, appId: String = BuildConfig.APPLICATION_ID) { try { @@ -375,7 +385,7 @@ fun openAppSettingsPage(activity: Activity) { } /** - * Ask the user to select a location and a file name to write in + * Ask the user to select a location and a file name to write in. */ fun selectTxtFileToWrite( activity: Activity, @@ -408,10 +418,11 @@ fun selectTxtFileToWrite( * @param sourceFile the file source path * @param dstDirPath the dst path * @param outputFilename optional the output filename + * @param currentTimeMillis the current time in milliseconds * @return the created file */ @Suppress("DEPRECATION") -fun saveFileIntoLegacy(sourceFile: File, dstDirPath: File, outputFilename: String?): File? { +fun saveFileIntoLegacy(sourceFile: File, dstDirPath: File, outputFilename: String?, currentTimeMillis: Long): File? { // defines another name for the external media var dstFileName: String @@ -423,7 +434,7 @@ fun saveFileIntoLegacy(sourceFile: File, dstDirPath: File, outputFilename: Strin if (dotPos > 0) { fileExt = sourceFile.name.substring(dotPos) } - dstFileName = "vector_" + System.currentTimeMillis() + fileExt + dstFileName = "vector_$currentTimeMillis$fileExt" } else { dstFileName = outputFilename } diff --git a/vector/src/main/java/im/vector/app/core/utils/FileUtils.kt b/vector/src/main/java/im/vector/app/core/utils/FileUtils.kt index 8d0c308202..c735b8b33d 100644 --- a/vector/src/main/java/im/vector/app/core/utils/FileUtils.kt +++ b/vector/src/main/java/im/vector/app/core/utils/FileUtils.kt @@ -68,7 +68,7 @@ private fun logAction(file: File): Boolean { * ========================================================================================== */ /** - * Return true in case of success + * Return true in case of success. */ private fun recursiveActionOnFile(file: File, action: ActionOnFile): Boolean { if (file.isDirectory) { @@ -86,7 +86,7 @@ private fun recursiveActionOnFile(file: File, action: ActionOnFile): Boolean { } /** - * Get the file extension of a fileUri or a filename + * Get the file extension of a fileUri or a filename. * * @param fileUri the fileUri (can be a simple filename) * @return the file extension, in lower case, or null is extension is not available or empty diff --git a/vector/src/main/java/im/vector/app/core/utils/PermissionsTools.kt b/vector/src/main/java/im/vector/app/core/utils/PermissionsTools.kt index eada3a4f25..b4f8de2485 100644 --- a/vector/src/main/java/im/vector/app/core/utils/PermissionsTools.kt +++ b/vector/src/main/java/im/vector/app/core/utils/PermissionsTools.kt @@ -142,7 +142,7 @@ fun checkPermissions(permissionsToBeGranted: List, } /** - * To be call after the permission request + * To be call after the permission request. * * @param permissionsToBeGranted the permissions to be granted * @param activity the calling Activity that is requesting the permissions (or fragment parent) diff --git a/vector/src/main/java/im/vector/app/core/utils/ReadOnce.kt b/vector/src/main/java/im/vector/app/core/utils/ReadOnce.kt index 4283ecefab..8c627cf55c 100644 --- a/vector/src/main/java/im/vector/app/core/utils/ReadOnce.kt +++ b/vector/src/main/java/im/vector/app/core/utils/ReadOnce.kt @@ -19,7 +19,7 @@ package im.vector.app.core.utils import java.util.concurrent.atomic.AtomicBoolean /** - * Use this container to read a value only once + * Use this container to read a value only once. */ class ReadOnce( private val value: T @@ -36,7 +36,7 @@ class ReadOnce( } /** - * Only the first call to isTrue() will return true + * Only the first call to isTrue() will return true. */ class ReadOnceTrue { private val readOnce = ReadOnce(true) diff --git a/vector/src/main/java/im/vector/app/core/utils/RingtoneUtils.kt b/vector/src/main/java/im/vector/app/core/utils/RingtoneUtils.kt index 9b84ea7b2f..a0fd3addac 100644 --- a/vector/src/main/java/im/vector/app/core/utils/RingtoneUtils.kt +++ b/vector/src/main/java/im/vector/app/core/utils/RingtoneUtils.kt @@ -101,14 +101,14 @@ fun setCallRingtoneUri(context: Context, ringtoneUri: Uri) { } /** - * Set using Riot default ringtone + * Set using Riot default ringtone. */ fun useRiotDefaultRingtone(context: Context): Boolean { return DefaultSharedPreferences.getInstance(context).getBoolean(VectorPreferences.SETTINGS_CALL_RINGTONE_USE_RIOT_PREFERENCE_KEY, true) } /** - * Ask if default Riot ringtone has to be used + * Ask if default Riot ringtone has to be used. */ fun setUseRiotDefaultRingtone(context: Context, useRiotDefault: Boolean) { DefaultSharedPreferences.getInstance(context) diff --git a/vector/src/main/java/im/vector/app/core/utils/SystemUtils.kt b/vector/src/main/java/im/vector/app/core/utils/SystemUtils.kt index 1fa2b8151a..18a467d8d0 100644 --- a/vector/src/main/java/im/vector/app/core/utils/SystemUtils.kt +++ b/vector/src/main/java/im/vector/app/core/utils/SystemUtils.kt @@ -43,21 +43,20 @@ import im.vector.app.features.notifications.NotificationUtils * This user option appears on Android M but Android O enforces its usage and kills apps not * authorised by the user to run in background. * - * @param context the context * @return true if battery optimisations are ignored */ -fun isIgnoringBatteryOptimizations(context: Context): Boolean { +fun Context.isIgnoringBatteryOptimizations(): Boolean { // no issue before Android M, battery optimisations did not exist return Build.VERSION.SDK_INT < Build.VERSION_CODES.M || - context.getSystemService()?.isIgnoringBatteryOptimizations(context.packageName) == true + getSystemService()?.isIgnoringBatteryOptimizations(packageName) == true } -fun isAirplaneModeOn(context: Context): Boolean { - return Settings.Global.getInt(context.contentResolver, Settings.Global.AIRPLANE_MODE_ON, 0) != 0 +fun Context.isAirplaneModeOn(): Boolean { + return Settings.Global.getInt(contentResolver, Settings.Global.AIRPLANE_MODE_ON, 0) != 0 } -fun isAnimationDisabled(context: Context): Boolean { - return Settings.Global.getFloat(context.contentResolver, Settings.Global.ANIMATOR_DURATION_SCALE, 1f) == 0f +fun Context.isAnimationEnabled(): Boolean { + return Settings.Global.getFloat(contentResolver, Settings.Global.ANIMATOR_DURATION_SCALE, 1f) != 0f } /** @@ -80,7 +79,7 @@ fun requestDisablingBatteryOptimization(activity: Activity, activityResultLaunch // ============================================================================================================== /** - * Copy a text to the clipboard, and display a Toast when done + * Copy a text to the clipboard, and display a Toast when done. * * @param context the context * @param text the text to copy diff --git a/vector/src/main/java/im/vector/app/core/utils/TemporaryStore.kt b/vector/src/main/java/im/vector/app/core/utils/TemporaryStore.kt index 63f80341eb..fb386e0876 100644 --- a/vector/src/main/java/im/vector/app/core/utils/TemporaryStore.kt +++ b/vector/src/main/java/im/vector/app/core/utils/TemporaryStore.kt @@ -22,7 +22,7 @@ import java.util.TimerTask const val THREE_MINUTES = 3 * 60_000L /** - * Store an object T for a specific period of time + * Store an object T for a specific period of time. * @param delay delay to keep the data, in millis */ open class TemporaryStore(private val delay: Long = THREE_MINUTES) { diff --git a/vector/src/main/java/im/vector/app/core/utils/ToolbarConfig.kt b/vector/src/main/java/im/vector/app/core/utils/ToolbarConfig.kt index 53bc60a4a6..620cad063c 100644 --- a/vector/src/main/java/im/vector/app/core/utils/ToolbarConfig.kt +++ b/vector/src/main/java/im/vector/app/core/utils/ToolbarConfig.kt @@ -34,7 +34,7 @@ class ToolbarConfig(val activity: AppCompatActivity, val toolbar: MaterialToolba } /** - * Delegating property for [activity.supportActionBar?.title] + * Delegating property for [activity.supportActionBar?.title]. */ var title: CharSequence? set(value) { @@ -43,7 +43,7 @@ class ToolbarConfig(val activity: AppCompatActivity, val toolbar: MaterialToolba get() = activity.supportActionBar?.title /** - * Delegating property for [activity.supportActionBar?.subtitle] + * Delegating property for [activity.supportActionBar?.subtitle]. */ var subtitle: CharSequence? set(value) { @@ -52,27 +52,27 @@ class ToolbarConfig(val activity: AppCompatActivity, val toolbar: MaterialToolba get() = activity.supportActionBar?.subtitle /** - * Sets toolbar's title text + * Sets toolbar's title text. */ fun setTitle(title: CharSequence?) = apply { activity.supportActionBar?.title = title } /** - * Sets toolbar's title text using provided string resource + * Sets toolbar's title text using provided string resource. */ fun setTitle(@StringRes titleRes: Int) = apply { activity.supportActionBar?.setTitle(titleRes) } /** - * Sets toolbar's subtitle text + * Sets toolbar's subtitle text. */ fun setSubtitle(subtitle: CharSequence?) = apply { activity.supportActionBar?.subtitle = subtitle } /** - * Sets toolbar's title text using provided string resource + * Sets toolbar's title text using provided string resource. */ fun setSubtitle(@StringRes subtitleRes: Int) = apply { activity.supportActionBar?.setSubtitle(subtitleRes) } /** - * Enables/disables navigate back button + * Enables/disables navigate back button. * * @param isAllowed defines if back button is enabled. Default [true] * @param useCross defines if cross icon should be used instead of arrow. Default [false] diff --git a/vector/src/main/java/im/vector/app/core/utils/ViewUtils.kt b/vector/src/main/java/im/vector/app/core/utils/ViewUtils.kt index 0e10a35981..aa15697aa1 100644 --- a/vector/src/main/java/im/vector/app/core/utils/ViewUtils.kt +++ b/vector/src/main/java/im/vector/app/core/utils/ViewUtils.kt @@ -23,7 +23,7 @@ import com.google.android.material.textfield.TextInputLayout import im.vector.app.core.platform.SimpleTextWatcher /** - * Find all TextInputLayout in a ViewGroup and in all its descendants + * Find all TextInputLayout in a ViewGroup and in all its descendants. */ fun ViewGroup.findAllTextInputLayout(): List { val res = ArrayList() @@ -41,7 +41,7 @@ fun ViewGroup.findAllTextInputLayout(): List { } /** - * Add a text change listener to all TextInputEditText to reset error on its TextInputLayout when the text is changed + * Add a text change listener to all TextInputEditText to reset error on its TextInputLayout when the text is changed. */ fun autoResetTextInputLayoutErrors(textInputLayouts: List) { textInputLayouts.forEach { diff --git a/vector/src/main/java/im/vector/app/features/MainActivity.kt b/vector/src/main/java/im/vector/app/features/MainActivity.kt index 42bd2318b3..17bf02dc4f 100644 --- a/vector/src/main/java/im/vector/app/features/MainActivity.kt +++ b/vector/src/main/java/im/vector/app/features/MainActivity.kt @@ -64,9 +64,9 @@ data class MainActivityArgs( ) : Parcelable /** - * This is the entry point of Element Android + * This is the entry point of Element Android. * This Activity, when started with argument, is also doing some cleanup when user signs out, - * clears cache, is logged out, or is soft logged out + * clears cache, is logged out, or is soft logged out. */ @AndroidEntryPoint class MainActivity : VectorBaseActivity(), UnlockedActivity { @@ -160,7 +160,7 @@ class MainActivity : VectorBaseActivity(), UnlockedActivity args.clearCredentials -> { lifecycleScope.launch { try { - session.signOut(!args.isUserLoggedOut) + session.signOutService().signOut(!args.isUserLoggedOut) } catch (failure: Throwable) { displayError(failure) return@launch 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 b2a62a42d3..42693a53f9 100644 --- a/vector/src/main/java/im/vector/app/features/VectorFeatures.kt +++ b/vector/src/main/java/im/vector/app/features/VectorFeatures.kt @@ -27,6 +27,7 @@ interface VectorFeatures { fun isOnboardingPersonalizeEnabled(): Boolean fun isOnboardingCombinedRegisterEnabled(): Boolean fun isLiveLocationEnabled(): Boolean + fun isScreenSharingEnabled(): Boolean enum class OnboardingVariant { LEGACY, @@ -43,4 +44,5 @@ class DefaultVectorFeatures : VectorFeatures { override fun isOnboardingPersonalizeEnabled() = false override fun isOnboardingCombinedRegisterEnabled() = false override fun isLiveLocationEnabled(): Boolean = false + override fun isScreenSharingEnabled(): Boolean = true } diff --git a/vector/src/main/java/im/vector/app/features/analytics/AnalyticsTracker.kt b/vector/src/main/java/im/vector/app/features/analytics/AnalyticsTracker.kt index 2389fbd724..871782e473 100644 --- a/vector/src/main/java/im/vector/app/features/analytics/AnalyticsTracker.kt +++ b/vector/src/main/java/im/vector/app/features/analytics/AnalyticsTracker.kt @@ -22,17 +22,17 @@ import im.vector.app.features.analytics.plan.UserProperties interface AnalyticsTracker { /** - * Capture an Event + * Capture an Event. */ fun capture(event: VectorAnalyticsEvent) /** - * Track a displayed screen + * Track a displayed screen. */ fun screen(screen: VectorAnalyticsScreen) /** - * Update user specific properties + * Update user specific properties. */ fun updateUserProperties(userProperties: UserProperties) } diff --git a/vector/src/main/java/im/vector/app/features/analytics/DecryptionFailureTracker.kt b/vector/src/main/java/im/vector/app/features/analytics/DecryptionFailureTracker.kt index 6b2ceb1444..e020ec1cf7 100644 --- a/vector/src/main/java/im/vector/app/features/analytics/DecryptionFailureTracker.kt +++ b/vector/src/main/java/im/vector/app/features/analytics/DecryptionFailureTracker.kt @@ -38,6 +38,7 @@ private data class DecryptionFailure( val failedEventId: String, val error: MXCryptoError.ErrorType ) +private typealias DetailedErrorName = Pair private const val GRACE_PERIOD_MILLIS = 4_000 private const val CHECK_INTERVAL = 2_000L @@ -85,7 +86,7 @@ class DecryptionFailureTracker @Inject constructor( /** * Can be called when the timeline is disposed in order - * to grace those events as they are not anymore displayed on screen + * to grace those events as they are not anymore displayed on screen. * */ fun onTimeLineDisposed(roomId: String) { scope.launch(Dispatchers.Default) { @@ -112,7 +113,7 @@ class DecryptionFailureTracker @Inject constructor( private fun checkFailures() { val now = clock.epochMillis() - val aggregatedErrors: Map> + val aggregatedErrors: Map> synchronized(failures) { val toReport = mutableListOf() failures.removeAll { failure -> @@ -136,20 +137,21 @@ class DecryptionFailureTracker @Inject constructor( // for now we ignore events already reported even if displayed again? .filter { alreadyReported.contains(it).not() } .forEach { failedEventId -> - analyticsTracker.capture(Error(failedEventId, Error.Domain.E2EE, aggregation.key)) + analyticsTracker.capture(Error(aggregation.key.first, Error.Domain.E2EE, aggregation.key.second)) alreadyReported.add(failedEventId) } } } - private fun MXCryptoError.ErrorType.toAnalyticsErrorName(): Error.Name { - return when (this) { - MXCryptoError.ErrorType.UNKNOWN_INBOUND_SESSION_ID -> Error.Name.OlmKeysNotSentError - MXCryptoError.ErrorType.OLM -> { - Error.Name.OlmUnspecifiedError - } - MXCryptoError.ErrorType.UNKNOWN_MESSAGE_INDEX -> Error.Name.OlmIndexError - else -> Error.Name.UnknownError + private fun MXCryptoError.ErrorType.toAnalyticsErrorName(): DetailedErrorName { + val detailed = "$name | mxc_crypto_error_type" + val errorName = when (this) { + MXCryptoError.ErrorType.UNKNOWN_INBOUND_SESSION_ID, + MXCryptoError.ErrorType.KEYS_WITHHELD -> Error.Name.OlmKeysNotSentError + MXCryptoError.ErrorType.OLM -> Error.Name.OlmUnspecifiedError + MXCryptoError.ErrorType.UNKNOWN_MESSAGE_INDEX -> Error.Name.OlmIndexError + else -> Error.Name.UnknownError } + return DetailedErrorName(detailed, errorName) } } diff --git a/vector/src/main/java/im/vector/app/features/analytics/VectorAnalytics.kt b/vector/src/main/java/im/vector/app/features/analytics/VectorAnalytics.kt index 95322412bd..7d11f93883 100644 --- a/vector/src/main/java/im/vector/app/features/analytics/VectorAnalytics.kt +++ b/vector/src/main/java/im/vector/app/features/analytics/VectorAnalytics.kt @@ -20,42 +20,42 @@ import kotlinx.coroutines.flow.Flow interface VectorAnalytics : AnalyticsTracker { /** - * Return a Flow of Boolean, true if the user has given their consent + * Return a Flow of Boolean, true if the user has given their consent. */ fun getUserConsent(): Flow /** - * Update the user consent value + * Update the user consent value. */ suspend fun setUserConsent(userConsent: Boolean) /** - * Return a Flow of Boolean, true if the user has been asked for their consent + * Return a Flow of Boolean, true if the user has been asked for their consent. */ fun didAskUserConsent(): Flow /** - * Store the fact that the user has been asked for their consent + * Store the fact that the user has been asked for their consent. */ suspend fun setDidAskUserConsent() /** - * Return a Flow of String, used for analytics Id + * Return a Flow of String, used for analytics Id. */ fun getAnalyticsId(): Flow /** - * Update analyticsId from the AccountData + * Update analyticsId from the AccountData. */ suspend fun setAnalyticsId(analyticsId: String) /** - * To be called when a session is destroyed + * To be called when a session is destroyed. */ suspend fun onSignOut() /** - * To be called when application is started + * To be called when application is started. */ fun init() } diff --git a/vector/src/main/java/im/vector/app/features/analytics/accountdata/AnalyticsAccountDataViewModel.kt b/vector/src/main/java/im/vector/app/features/analytics/accountdata/AnalyticsAccountDataViewModel.kt index 3b92e7c4de..221a9d8843 100644 --- a/vector/src/main/java/im/vector/app/features/analytics/accountdata/AnalyticsAccountDataViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/analytics/accountdata/AnalyticsAccountDataViewModel.kt @@ -66,7 +66,7 @@ class AnalyticsAccountDataViewModel @AssistedInject constructor( private fun observeInitSync() { combine( - session.getSyncStatusLive().asFlow(), + session.syncStatusService().getSyncStatusLive().asFlow(), analytics.getUserConsent(), analytics.getAnalyticsId() ) { status, userConsent, analyticsId -> diff --git a/vector/src/main/java/im/vector/app/features/analytics/extensions/JoinedRoomExt.kt b/vector/src/main/java/im/vector/app/features/analytics/extensions/JoinedRoomExt.kt index c13f8295f2..27ce3e9a23 100644 --- a/vector/src/main/java/im/vector/app/features/analytics/extensions/JoinedRoomExt.kt +++ b/vector/src/main/java/im/vector/app/features/analytics/extensions/JoinedRoomExt.kt @@ -33,18 +33,20 @@ fun Int?.toAnalyticsRoomSize(): JoinedRoom.RoomSize { } } -fun RoomSummary?.toAnalyticsJoinedRoom(): JoinedRoom { +fun RoomSummary?.toAnalyticsJoinedRoom(trigger: JoinedRoom.Trigger?): JoinedRoom { return JoinedRoom( isDM = this?.isDirect.orFalse(), isSpace = this?.roomType == RoomType.SPACE, - roomSize = this?.joinedMembersCount?.toAnalyticsRoomSize() ?: JoinedRoom.RoomSize.Two + roomSize = this?.joinedMembersCount?.toAnalyticsRoomSize() ?: JoinedRoom.RoomSize.Two, + trigger = trigger ) } -fun PublicRoom.toAnalyticsJoinedRoom(): JoinedRoom { +fun PublicRoom.toAnalyticsJoinedRoom(trigger: JoinedRoom.Trigger?): JoinedRoom { return JoinedRoom( isDM = false, isSpace = false, - roomSize = numJoinedMembers.toAnalyticsRoomSize() + roomSize = numJoinedMembers.toAnalyticsRoomSize(), + trigger = trigger ) } diff --git a/vector/src/main/java/im/vector/app/features/analytics/extensions/ViewRoomExt.kt b/vector/src/main/java/im/vector/app/features/analytics/extensions/ViewRoomExt.kt new file mode 100644 index 0000000000..ada936a3b5 --- /dev/null +++ b/vector/src/main/java/im/vector/app/features/analytics/extensions/ViewRoomExt.kt @@ -0,0 +1,44 @@ +/* + * 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.analytics.extensions + +import im.vector.app.RoomGroupingMethod +import im.vector.app.features.analytics.plan.ViewRoom +import org.matrix.android.sdk.api.extensions.orFalse +import org.matrix.android.sdk.api.session.room.model.RoomSummary +import org.matrix.android.sdk.api.session.room.model.RoomType + +fun RoomSummary?.toAnalyticsViewRoom(trigger: ViewRoom.Trigger?, groupingMethod: RoomGroupingMethod? = null, viaKeyboard: Boolean? = null): ViewRoom { + val activeSpace = groupingMethod?.let { + when (it) { + is RoomGroupingMethod.BySpace -> it.spaceSummary?.toActiveSpace() ?: ViewRoom.ActiveSpace.Home + else -> null + } + } + + return ViewRoom( + isDM = this?.isDirect.orFalse(), + isSpace = this?.roomType == RoomType.SPACE, + trigger = trigger, + activeSpace = activeSpace, + viaKeyboard = viaKeyboard + ) +} + +private fun RoomSummary.toActiveSpace(): ViewRoom.ActiveSpace { + return if (isPublic) ViewRoom.ActiveSpace.Public else ViewRoom.ActiveSpace.Private +} diff --git a/vector/src/main/java/im/vector/app/features/analytics/impl/DefaultVectorAnalytics.kt b/vector/src/main/java/im/vector/app/features/analytics/impl/DefaultVectorAnalytics.kt index 7b653ef44b..87f0e8c13b 100644 --- a/vector/src/main/java/im/vector/app/features/analytics/impl/DefaultVectorAnalytics.kt +++ b/vector/src/main/java/im/vector/app/features/analytics/impl/DefaultVectorAnalytics.kt @@ -158,8 +158,8 @@ class DefaultVectorAnalytics @Inject constructor( } /** - * We avoid sending nulls as part of the UserProperties as this will reset the values across all devices - * The UserProperties event has nullable properties to allow for clients to opt in + * We avoid sending nulls as part of the UserProperties as this will reset the values across all devices. + * The UserProperties event has nullable properties to allow for clients to opt in. */ private fun Map.toPostHogUserProperties(): Properties { return Properties().apply { diff --git a/vector/src/main/java/im/vector/app/features/analytics/plan/Error.kt b/vector/src/main/java/im/vector/app/features/analytics/plan/Error.kt index a926776680..da80787a65 100644 --- a/vector/src/main/java/im/vector/app/features/analytics/plan/Error.kt +++ b/vector/src/main/java/im/vector/app/features/analytics/plan/Error.kt @@ -22,11 +22,11 @@ import im.vector.app.features.analytics.itf.VectorAnalyticsEvent // https://github.com/matrix-org/matrix-analytics-events/ /** - * Triggered when an error occurred + * Triggered when an error occurred. */ data class Error( /** - * Context - client defined, can be used for debugging + * Context - client defined, can be used for debugging. */ val context: String? = null, val domain: Domain, diff --git a/vector/src/main/java/im/vector/app/features/analytics/plan/Interaction.kt b/vector/src/main/java/im/vector/app/features/analytics/plan/Interaction.kt index 2007f75fbc..4e4bfe53be 100644 --- a/vector/src/main/java/im/vector/app/features/analytics/plan/Interaction.kt +++ b/vector/src/main/java/im/vector/app/features/analytics/plan/Interaction.kt @@ -87,12 +87,30 @@ data class Interaction( */ WebAddExistingToSpaceDialogCreateRoomButton, + /** + * User clicked the create DM button in the home page of Element + * Web/Desktop. + */ + WebHomeCreateChatButton, + /** * User clicked the create room button in the home page of Element * Web/Desktop. */ WebHomeCreateRoomButton, + /** + * User clicked the explore rooms button in the home page of Element + * Web/Desktop. + */ + WebHomeExploreRoomsButton, + + /** + * User clicked the explore rooms button next to the search field at the + * top of the left panel in Element Web/Desktop. + */ + WebLeftPanelExploreRoomsButton, + /** * User interacted with pin to sidebar checkboxes in the quick settings * menu of Element Web/Desktop. @@ -189,6 +207,12 @@ data class Interaction( */ WebRoomHeaderContextMenuSettingsItem, + /** + * User clicked the create DM button in the + context menu of the room + * list header in Element Web/Desktop. + */ + WebRoomListHeaderPlusMenuCreateChatItem, + /** * User clicked the create room button in the + context menu of the room * list header in Element Web/Desktop. @@ -231,6 +255,12 @@ data class Interaction( */ WebRoomListRoomTileNotificationsMenu, + /** + * User clicked the create DM button in the + context menu of the rooms + * sublist in Element Web/Desktop. + */ + WebRoomListRoomsSublistPlusMenuCreateChatItem, + /** * User clicked the create room button in the + context menu of the * rooms sublist in Element Web/Desktop. diff --git a/vector/src/main/java/im/vector/app/features/analytics/plan/JoinedRoom.kt b/vector/src/main/java/im/vector/app/features/analytics/plan/JoinedRoom.kt index 0011d0e144..b076a07ae0 100644 --- a/vector/src/main/java/im/vector/app/features/analytics/plan/JoinedRoom.kt +++ b/vector/src/main/java/im/vector/app/features/analytics/plan/JoinedRoom.kt @@ -50,12 +50,7 @@ data class JoinedRoom( Invite, /** - * Room joined via space explore - */ - MobileExploreRooms, - - /** - * Room joined via link + * Room joined via link. */ MobilePermalink, diff --git a/vector/src/main/java/im/vector/app/features/analytics/plan/MobileScreen.kt b/vector/src/main/java/im/vector/app/features/analytics/plan/MobileScreen.kt index 79bae544ec..3ce3dfb578 100644 --- a/vector/src/main/java/im/vector/app/features/analytics/plan/MobileScreen.kt +++ b/vector/src/main/java/im/vector/app/features/analytics/plan/MobileScreen.kt @@ -22,7 +22,7 @@ import im.vector.app.features.analytics.itf.VectorAnalyticsScreen // https://github.com/matrix-org/matrix-analytics-events/ /** - * Triggered when the user changed screen on Element Android/iOS + * Triggered when the user changed screen on Element Android/iOS. */ data class MobileScreen( /** @@ -59,7 +59,7 @@ data class MobileScreen( Favourites, /** - * The form for the forgot password use case + * The form for the forgot password use case. */ ForgotPassword, @@ -96,7 +96,7 @@ data class MobileScreen( /** * The screen that displays the registration flow (when the user wants - * to create an account) + * to create an account). */ Register, @@ -143,7 +143,7 @@ data class MobileScreen( RoomPermissions, /** - * Screen that displays room preview if user hasn't joined yet + * Screen that displays room preview if user hasn't joined yet. */ RoomPreview, @@ -197,7 +197,7 @@ data class MobileScreen( /** * The advanced settings screen (developer mode, rageshake, push - * notification rules) + * notification rules). */ SettingsAdvanced, @@ -212,7 +212,7 @@ data class MobileScreen( SettingsGeneral, /** - * The Help and About screen + * The Help and About screen. */ SettingsHelp, @@ -222,12 +222,12 @@ data class MobileScreen( SettingsIgnoredUsers, /** - * The experimental features settings screen, + * The experimental features settings screen. */ SettingsLabs, /** - * The settings screen with legals information + * The settings screen with legals information. */ SettingsLegals, @@ -262,17 +262,17 @@ data class MobileScreen( Sidebar, /** - * Screen that displays the list of rooms and spaces of a space + * Screen that displays the list of rooms and spaces of a space. */ SpaceExploreRooms, /** - * Screen that displays the list of members of a space + * Screen that displays the list of members of a space. */ SpaceMembers, /** - * The bottom sheet that list all space options + * The bottom sheet that list all space options. */ SpaceMenu, @@ -287,7 +287,7 @@ data class MobileScreen( SwitchDirectory, /** - * Screen that displays list of threads for a room + * Screen that displays list of threads for a room. */ ThreadList, diff --git a/vector/src/main/java/im/vector/app/features/analytics/plan/UserProperties.kt b/vector/src/main/java/im/vector/app/features/analytics/plan/UserProperties.kt index dea499edde..77be2456cd 100644 --- a/vector/src/main/java/im/vector/app/features/analytics/plan/UserProperties.kt +++ b/vector/src/main/java/im/vector/app/features/analytics/plan/UserProperties.kt @@ -25,35 +25,35 @@ package im.vector.app.features.analytics.plan */ data class UserProperties( /** - * Whether the user has the favourites space enabled + * Whether the user has the favourites space enabled. */ - val WebMetaSpaceFavouritesEnabled: Boolean? = null, + val webMetaSpaceFavouritesEnabled: Boolean? = null, /** - * Whether the user has the home space set to all rooms + * Whether the user has the home space set to all rooms. */ - val WebMetaSpaceHomeAllRooms: Boolean? = null, + val webMetaSpaceHomeAllRooms: Boolean? = null, /** - * Whether the user has the home space enabled + * Whether the user has the home space enabled. */ - val WebMetaSpaceHomeEnabled: Boolean? = null, + val webMetaSpaceHomeEnabled: Boolean? = null, /** - * Whether the user has the other rooms space enabled + * Whether the user has the other rooms space enabled. */ - val WebMetaSpaceOrphansEnabled: Boolean? = null, + val webMetaSpaceOrphansEnabled: Boolean? = null, /** - * Whether the user has the people space enabled + * Whether the user has the people space enabled. */ - val WebMetaSpacePeopleEnabled: Boolean? = null, + val webMetaSpacePeopleEnabled: Boolean? = null, /** * The selected messaging use case during the onboarding flow. */ val ftueUseCaseSelection: FtueUseCaseSelection? = null, /** - * Number of joined rooms the user has favourited + * Number of joined rooms the user has favourited. */ val numFavouriteRooms: Int? = null, /** - * Number of spaces (and sub-spaces) the user is joined to + * Number of spaces (and sub-spaces) the user is joined to. */ val numSpaces: Int? = null, ) { @@ -82,11 +82,11 @@ data class UserProperties( fun getProperties(): Map? { return mutableMapOf().apply { - WebMetaSpaceFavouritesEnabled?.let { put("WebMetaSpaceFavouritesEnabled", it) } - WebMetaSpaceHomeAllRooms?.let { put("WebMetaSpaceHomeAllRooms", it) } - WebMetaSpaceHomeEnabled?.let { put("WebMetaSpaceHomeEnabled", it) } - WebMetaSpaceOrphansEnabled?.let { put("WebMetaSpaceOrphansEnabled", it) } - WebMetaSpacePeopleEnabled?.let { put("WebMetaSpacePeopleEnabled", it) } + webMetaSpaceFavouritesEnabled?.let { put("WebMetaSpaceFavouritesEnabled", it) } + webMetaSpaceHomeAllRooms?.let { put("WebMetaSpaceHomeAllRooms", it) } + webMetaSpaceHomeEnabled?.let { put("WebMetaSpaceHomeEnabled", it) } + webMetaSpaceOrphansEnabled?.let { put("WebMetaSpaceOrphansEnabled", it) } + webMetaSpacePeopleEnabled?.let { put("WebMetaSpacePeopleEnabled", it) } ftueUseCaseSelection?.let { put("ftueUseCaseSelection", it.name) } numFavouriteRooms?.let { put("numFavouriteRooms", it) } numSpaces?.let { put("numSpaces", it) } diff --git a/vector/src/main/java/im/vector/app/features/analytics/plan/ViewRoom.kt b/vector/src/main/java/im/vector/app/features/analytics/plan/ViewRoom.kt index e9bef6f1d3..d2f30eec9b 100644 --- a/vector/src/main/java/im/vector/app/features/analytics/plan/ViewRoom.kt +++ b/vector/src/main/java/im/vector/app/features/analytics/plan/ViewRoom.kt @@ -64,7 +64,7 @@ data class ViewRoom( MessageUser, /** - * Room accessed via space explore + * Room accessed via space explore. */ MobileExploreRooms, @@ -79,12 +79,12 @@ data class ViewRoom( MobileInCall, /** - * Room accessed during external sharing + * Room accessed during external sharing. */ MobileLinkShare, /** - * Room accessed via link + * Room accessed via link. */ MobilePermalink, @@ -95,7 +95,7 @@ data class ViewRoom( MobileRoomMemberDetail, /** - * Room accessed via preview + * Room accessed via preview. */ MobileRoomPreview, @@ -117,7 +117,7 @@ data class ViewRoom( MobileSpaceMemberDetail, /** - * Room accessed via space members list + * Room accessed via space members list. */ MobileSpaceMembers, diff --git a/vector/src/main/java/im/vector/app/features/analytics/store/AnalyticsStore.kt b/vector/src/main/java/im/vector/app/features/analytics/store/AnalyticsStore.kt index d732e27a82..823d6285ed 100644 --- a/vector/src/main/java/im/vector/app/features/analytics/store/AnalyticsStore.kt +++ b/vector/src/main/java/im/vector/app/features/analytics/store/AnalyticsStore.kt @@ -33,9 +33,9 @@ private val Context.dataStore: DataStore by preferencesDataStore(na /** * Local storage for: - * - user consent (Boolean) - * - did ask user consent (Boolean) - * - analytics Id (String) + * - user consent (Boolean); + * - did ask user consent (Boolean); + * - analytics Id (String). */ class AnalyticsStore @Inject constructor( private val context: Context diff --git a/vector/src/main/java/im/vector/app/features/analytics/ui/consent/AnalyticsOptInActivity.kt b/vector/src/main/java/im/vector/app/features/analytics/ui/consent/AnalyticsOptInActivity.kt index c11cf582d3..39458f84a3 100644 --- a/vector/src/main/java/im/vector/app/features/analytics/ui/consent/AnalyticsOptInActivity.kt +++ b/vector/src/main/java/im/vector/app/features/analytics/ui/consent/AnalyticsOptInActivity.kt @@ -25,7 +25,7 @@ import im.vector.app.databinding.ActivitySimpleBinding import javax.inject.Inject /** - * Simple container for AnalyticsOptInFragment + * Simple container for AnalyticsOptInFragment. */ @AndroidEntryPoint class AnalyticsOptInActivity : VectorBaseActivity() { diff --git a/vector/src/main/java/im/vector/app/features/analytics/ui/consent/AnalyticsOptInFragment.kt b/vector/src/main/java/im/vector/app/features/analytics/ui/consent/AnalyticsOptInFragment.kt index f112ba4659..320386aebd 100644 --- a/vector/src/main/java/im/vector/app/features/analytics/ui/consent/AnalyticsOptInFragment.kt +++ b/vector/src/main/java/im/vector/app/features/analytics/ui/consent/AnalyticsOptInFragment.kt @@ -31,7 +31,7 @@ import im.vector.app.databinding.FragmentAnalyticsOptinBinding import javax.inject.Inject class AnalyticsOptInFragment @Inject constructor() : - VectorBaseFragment(), + VectorBaseFragment(), OnBackPressed { // Share the view model with the Activity so that the Activity diff --git a/vector/src/main/java/im/vector/app/features/attachments/AttachmentTypeSelectorView.kt b/vector/src/main/java/im/vector/app/features/attachments/AttachmentTypeSelectorView.kt index 98440632d8..d1f0efae6c 100644 --- a/vector/src/main/java/im/vector/app/features/attachments/AttachmentTypeSelectorView.kt +++ b/vector/src/main/java/im/vector/app/features/attachments/AttachmentTypeSelectorView.kt @@ -140,22 +140,26 @@ class AttachmentTypeSelectorView(context: Context, private fun animateWindowInCircular(anchor: View, contentView: View) { val coordinates = getClickCoordinates(anchor, contentView) - val animator = ViewAnimationUtils.createCircularReveal(contentView, + val animator = ViewAnimationUtils.createCircularReveal( + contentView, coordinates.first, coordinates.second, 0f, - max(contentView.width, contentView.height).toFloat()) + max(contentView.width, contentView.height).toFloat() + ) animator.duration = ANIMATION_DURATION.toLong() animator.start() } private fun animateWindowOutCircular(anchor: View, contentView: View) { val coordinates = getClickCoordinates(anchor, contentView) - val animator = ViewAnimationUtils.createCircularReveal(getContentView(), + val animator = ViewAnimationUtils.createCircularReveal( + getContentView(), coordinates.first, coordinates.second, max(getContentView().width, getContentView().height).toFloat(), - 0f) + 0f + ) animator.duration = ANIMATION_DURATION.toLong() animator.addListener(object : AnimatorListenerAdapter() { @@ -207,7 +211,7 @@ class AttachmentTypeSelectorView(context: Context, } /** - * The all possible types to pick with their required permissions and tooltip resource + * The all possible types to pick with their required permissions and tooltip resource. */ enum class Type(val permissions: List, @StringRes val tooltipRes: Int) { CAMERA(PERMISSIONS_FOR_TAKING_PHOTO, R.string.tooltip_attachment_photo), diff --git a/vector/src/main/java/im/vector/app/features/attachments/AttachmentsHelper.kt b/vector/src/main/java/im/vector/app/features/attachments/AttachmentsHelper.kt index 28760bf52f..14324896bc 100644 --- a/vector/src/main/java/im/vector/app/features/attachments/AttachmentsHelper.kt +++ b/vector/src/main/java/im/vector/app/features/attachments/AttachmentsHelper.kt @@ -73,21 +73,21 @@ class AttachmentsHelper(val context: Context, val callback: Callback) : Restorab // Public Methods /** - * Starts the process for handling file picking + * Starts the process for handling file picking. */ fun selectFile(activityResultLauncher: ActivityResultLauncher) { MultiPicker.get(MultiPicker.FILE).startWith(activityResultLauncher) } /** - * Starts the process for handling image/video picking + * Starts the process for handling image/video picking. */ fun selectGallery(activityResultLauncher: ActivityResultLauncher) { MultiPicker.get(MultiPicker.MEDIA).startWith(activityResultLauncher) } /** - * Starts the process for handling audio picking + * Starts the process for handling audio picking. */ fun selectAudio(activityResultLauncher: ActivityResultLauncher) { MultiPicker.get(MultiPicker.AUDIO).startWith(activityResultLauncher) @@ -112,7 +112,7 @@ class AttachmentsHelper(val context: Context, val callback: Callback) : Restorab } /** - * Starts the process for handling contact picking + * Starts the process for handling contact picking. */ fun selectContact(activityResultLauncher: ActivityResultLauncher) { MultiPicker.get(MultiPicker.CONTACT).startWith(activityResultLauncher) diff --git a/vector/src/main/java/im/vector/app/features/attachments/preview/AttachmentsPreviewFragment.kt b/vector/src/main/java/im/vector/app/features/attachments/preview/AttachmentsPreviewFragment.kt index 5b9c3f7fb4..729ac10d4b 100644 --- a/vector/src/main/java/im/vector/app/features/attachments/preview/AttachmentsPreviewFragment.kt +++ b/vector/src/main/java/im/vector/app/features/attachments/preview/AttachmentsPreviewFragment.kt @@ -45,6 +45,7 @@ import im.vector.app.core.extensions.insertBeforeLast import im.vector.app.core.extensions.registerStartForActivityResult import im.vector.app.core.platform.VectorBaseFragment import im.vector.app.core.resources.ColorProvider +import im.vector.app.core.time.Clock import im.vector.app.core.utils.OnSnapPositionChangeListener import im.vector.app.core.utils.SnapOnScrollListener import im.vector.app.core.utils.attachSnapHelperWithListener @@ -64,7 +65,8 @@ data class AttachmentsPreviewArgs( class AttachmentsPreviewFragment @Inject constructor( private val attachmentMiniaturePreviewController: AttachmentMiniaturePreviewController, private val attachmentBigPreviewController: AttachmentBigPreviewController, - private val colorProvider: ColorProvider + private val colorProvider: ColorProvider, + private val clock: Clock, ) : VectorBaseFragment(), AttachmentMiniaturePreviewController.Callback { private val fragmentArgs: AttachmentsPreviewArgs by args() @@ -192,7 +194,7 @@ class AttachmentsPreviewFragment @Inject constructor( private fun handleEditAction() = withState(viewModel) { val currentAttachment = it.attachments.getOrNull(it.currentAttachmentIndex) ?: return@withState - val destinationFile = File(requireContext().cacheDir, currentAttachment.name.insertBeforeLast("_edited_image_${System.currentTimeMillis()}")) + val destinationFile = File(requireContext().cacheDir, currentAttachment.name.insertBeforeLast("_edited_image_${clock.epochMillis()}")) val uri = currentAttachment.queryUri createUCropWithDefaultSettings(colorProvider, uri, destinationFile.toUri(), currentAttachment.name) .getIntent(requireContext()) diff --git a/vector/src/main/java/im/vector/app/features/attachments/preview/AttachmentsPreviewViewModel.kt b/vector/src/main/java/im/vector/app/features/attachments/preview/AttachmentsPreviewViewModel.kt index 3e9d72e98b..81f0994899 100644 --- a/vector/src/main/java/im/vector/app/features/attachments/preview/AttachmentsPreviewViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/attachments/preview/AttachmentsPreviewViewModel.kt @@ -20,7 +20,7 @@ package im.vector.app.features.attachments.preview import im.vector.app.core.platform.VectorViewModel class AttachmentsPreviewViewModel(initialState: AttachmentsPreviewViewState) : - VectorViewModel(initialState) { + VectorViewModel(initialState) { override fun handle(action: AttachmentsPreviewAction) { when (action) { diff --git a/vector/src/main/java/im/vector/app/features/attachments/preview/Extensions.kt b/vector/src/main/java/im/vector/app/features/attachments/preview/Extensions.kt index 672cde977d..3d48be5f67 100644 --- a/vector/src/main/java/im/vector/app/features/attachments/preview/Extensions.kt +++ b/vector/src/main/java/im/vector/app/features/attachments/preview/Extensions.kt @@ -21,7 +21,7 @@ import org.matrix.android.sdk.api.util.MimeTypes import org.matrix.android.sdk.api.util.MimeTypes.isMimeTypeImage /** - * All images are editable, expect Gif + * All images are editable, expect Gif. */ fun ContentAttachmentData.isEditable(): Boolean { return type == ContentAttachmentData.Type.IMAGE && diff --git a/vector/src/main/java/im/vector/app/features/auth/PromptFragment.kt b/vector/src/main/java/im/vector/app/features/auth/PromptFragment.kt index 501af00ad2..bd44a5b9cd 100644 --- a/vector/src/main/java/im/vector/app/features/auth/PromptFragment.kt +++ b/vector/src/main/java/im/vector/app/features/auth/PromptFragment.kt @@ -65,7 +65,7 @@ class PromptFragment : VectorBaseFragment() { override fun invalidate() = withState(viewModel) { when (it.flowType) { - LoginFlowTypes.SSO -> { + LoginFlowTypes.SSO -> { views.passwordFieldTil.isVisible = false views.reAuthConfirmButton.text = getString(R.string.auth_login_sso) } diff --git a/vector/src/main/java/im/vector/app/features/auth/ReAuthActivity.kt b/vector/src/main/java/im/vector/app/features/auth/ReAuthActivity.kt index 78c6cbb021..f92ec8ea9e 100644 --- a/vector/src/main/java/im/vector/app/features/auth/ReAuthActivity.kt +++ b/vector/src/main/java/im/vector/app/features/auth/ReAuthActivity.kt @@ -86,10 +86,10 @@ class ReAuthActivity : SimpleFragmentActivity() { sharedViewModel.observeViewEvents { when (it) { - is ReAuthEvents.OpenSsoURl -> { + is ReAuthEvents.OpenSsoURl -> { openInCustomTab(it.url) } - ReAuthEvents.Dismiss -> { + ReAuthEvents.Dismiss -> { setResult(RESULT_CANCELED) finish() } @@ -205,7 +205,7 @@ class ReAuthActivity : SimpleFragmentActivity() { LoginFlowTypes.PASSWORD -> { LoginFlowTypes.PASSWORD } - LoginFlowTypes.SSO -> { + LoginFlowTypes.SSO -> { LoginFlowTypes.SSO } else -> { diff --git a/vector/src/main/java/im/vector/app/features/auth/ReAuthViewModel.kt b/vector/src/main/java/im/vector/app/features/auth/ReAuthViewModel.kt index fcc2a59ef8..0bd26870e5 100644 --- a/vector/src/main/java/im/vector/app/features/auth/ReAuthViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/auth/ReAuthViewModel.kt @@ -42,7 +42,7 @@ class ReAuthViewModel @AssistedInject constructor( override fun handle(action: ReAuthActions) = withState { state -> when (action) { - ReAuthActions.StartSSOFallback -> { + ReAuthActions.StartSSOFallback -> { if (state.flowType == LoginFlowTypes.SSO) { setState { copy(ssoFallbackPageWasShown = true) } val ssoURL = session.getUiaSsoFallbackUrl(initialState.session ?: "") @@ -55,10 +55,10 @@ class ReAuthViewModel @AssistedInject constructor( ReAuthActions.FallBackPageClosed -> { // Should we do something here? } - is ReAuthActions.ReAuthWithPass -> { + is ReAuthActions.ReAuthWithPass -> { val safeForIntentCypher = ByteArrayOutputStream().also { it.use { - session.securelyStoreObject(action.password, initialState.resultKeyStoreAlias, it) + session.secureStorageService().securelyStoreObject(action.password, initialState.resultKeyStoreAlias, it) } }.toByteArray().toBase64NoPadding() _viewEvents.post(ReAuthEvents.PasswordFinishSuccess(safeForIntentCypher)) diff --git a/vector/src/main/java/im/vector/app/features/autocomplete/AutocompleteClickListener.kt b/vector/src/main/java/im/vector/app/features/autocomplete/AutocompleteClickListener.kt index 50896c5f34..867269ae4a 100644 --- a/vector/src/main/java/im/vector/app/features/autocomplete/AutocompleteClickListener.kt +++ b/vector/src/main/java/im/vector/app/features/autocomplete/AutocompleteClickListener.kt @@ -17,7 +17,7 @@ package im.vector.app.features.autocomplete /** - * Simple generic listener interface + * Simple generic listener interface. */ interface AutocompleteClickListener { diff --git a/vector/src/main/java/im/vector/app/features/autocomplete/group/AutocompleteGroupPresenter.kt b/vector/src/main/java/im/vector/app/features/autocomplete/group/AutocompleteGroupPresenter.kt index a3c276c977..fc7479eb21 100644 --- a/vector/src/main/java/im/vector/app/features/autocomplete/group/AutocompleteGroupPresenter.kt +++ b/vector/src/main/java/im/vector/app/features/autocomplete/group/AutocompleteGroupPresenter.kt @@ -55,7 +55,7 @@ class AutocompleteGroupPresenter @Inject constructor(context: Context, QueryStringValue.Contains(query.toString(), QueryStringValue.Case.INSENSITIVE) } } - val groups = session.getGroupSummaries(queryParams) + val groups = session.groupService().getGroupSummaries(queryParams) .asSequence() .sortedBy { it.displayName } controller.setData(groups.toList()) diff --git a/vector/src/main/java/im/vector/app/features/autocomplete/member/AutocompleteMemberPresenter.kt b/vector/src/main/java/im/vector/app/features/autocomplete/member/AutocompleteMemberPresenter.kt index ce3b9c6a7e..88d015ec0c 100644 --- a/vector/src/main/java/im/vector/app/features/autocomplete/member/AutocompleteMemberPresenter.kt +++ b/vector/src/main/java/im/vector/app/features/autocomplete/member/AutocompleteMemberPresenter.kt @@ -24,10 +24,11 @@ import dagger.assisted.AssistedInject import im.vector.app.R import im.vector.app.features.autocomplete.AutocompleteClickListener import im.vector.app.features.autocomplete.RecyclerViewPresenter -import org.matrix.android.sdk.api.pushrules.SenderNotificationPermissionCondition import org.matrix.android.sdk.api.query.QueryStringValue import org.matrix.android.sdk.api.session.Session import org.matrix.android.sdk.api.session.events.model.Event +import org.matrix.android.sdk.api.session.getRoom +import org.matrix.android.sdk.api.session.pushrules.SenderNotificationPermissionCondition import org.matrix.android.sdk.api.session.room.members.RoomMemberQueryParams import org.matrix.android.sdk.api.session.room.members.roomMemberQueryParams import org.matrix.android.sdk.api.session.room.model.Membership @@ -126,7 +127,8 @@ class AutocompleteMemberPresenter @AssistedInject constructor(context: Context, ) private fun createMemberItems(queryParams: RoomMemberQueryParams) = - room.getRoomMembers(queryParams) + room.membershipService() + .getRoomMembers(queryParams) .asSequence() .sortedBy { it.displayName } .disambiguate() @@ -147,7 +149,7 @@ class AutocompleteMemberPresenter @AssistedInject constructor(context: Context, AutocompleteMemberItem.Everyone(it) } - private fun canNotifyEveryone() = session.resolveSenderNotificationPermissionCondition( + private fun canNotifyEveryone() = session.pushRuleService().resolveSenderNotificationPermissionCondition( Event( senderId = session.myUserId, roomId = roomId diff --git a/vector/src/main/java/im/vector/app/features/autocomplete/room/AutocompleteRoomPresenter.kt b/vector/src/main/java/im/vector/app/features/autocomplete/room/AutocompleteRoomPresenter.kt index 99f0af7184..d9310e295d 100644 --- a/vector/src/main/java/im/vector/app/features/autocomplete/room/AutocompleteRoomPresenter.kt +++ b/vector/src/main/java/im/vector/app/features/autocomplete/room/AutocompleteRoomPresenter.kt @@ -51,7 +51,7 @@ class AutocompleteRoomPresenter @Inject constructor(context: Context, QueryStringValue.Contains(query.toString(), QueryStringValue.Case.INSENSITIVE) } } - val rooms = session.getRoomSummaries(queryParams) + val rooms = session.roomService().getRoomSummaries(queryParams) .asSequence() .sortedBy { it.displayName } controller.setData(rooms.toList()) diff --git a/vector/src/main/java/im/vector/app/features/badge/BadgeProxy.kt b/vector/src/main/java/im/vector/app/features/badge/BadgeProxy.kt index fb597d1ef9..c15ec8bdf4 100644 --- a/vector/src/main/java/im/vector/app/features/badge/BadgeProxy.kt +++ b/vector/src/main/java/im/vector/app/features/badge/BadgeProxy.kt @@ -24,12 +24,12 @@ import me.leolin.shortcutbadger.ShortcutBadger import org.matrix.android.sdk.api.session.Session /** - * Manage application badge (displayed in the launcher) + * Manage application badge (displayed in the launcher). */ object BadgeProxy { /** - * Badge is now managed by notification channel, so no need to use compatibility library in recent versions + * Badge is now managed by notification channel, so no need to use compatibility library in recent versions. * * @return true if library ShortcutBadger can be used */ diff --git a/vector/src/main/java/im/vector/app/features/call/CallControlsBottomSheet.kt b/vector/src/main/java/im/vector/app/features/call/CallControlsBottomSheet.kt index e38b53c858..11a43bcd0c 100644 --- a/vector/src/main/java/im/vector/app/features/call/CallControlsBottomSheet.kt +++ b/vector/src/main/java/im/vector/app/features/call/CallControlsBottomSheet.kt @@ -27,6 +27,8 @@ import dagger.hilt.android.AndroidEntryPoint import im.vector.app.R import im.vector.app.core.platform.VectorBaseBottomSheetDialogFragment import im.vector.app.databinding.BottomSheetCallControlsBinding +import im.vector.app.features.VectorFeatures +import javax.inject.Inject @AndroidEntryPoint class CallControlsBottomSheet : VectorBaseBottomSheetDialogFragment() { @@ -34,6 +36,8 @@ class CallControlsBottomSheet : VectorBaseBottomSheetDialogFragment { diff --git a/vector/src/main/java/im/vector/app/features/call/CallProximityManager.kt b/vector/src/main/java/im/vector/app/features/call/CallProximityManager.kt index 3aeeac15af..78a5ff969a 100644 --- a/vector/src/main/java/im/vector/app/features/call/CallProximityManager.kt +++ b/vector/src/main/java/im/vector/app/features/call/CallProximityManager.kt @@ -85,7 +85,7 @@ class CallProximityManager @Inject constructor( } /** - * Recommending naming convention for WakeLock tags is "app:tag" + * Recommending naming convention for WakeLock tags is "app:tag". */ private fun generateWakeLockTag() = "${stringProvider.getString(R.string.app_name)}:$PROXIMITY_WAKE_LOCK_TAG" diff --git a/vector/src/main/java/im/vector/app/features/call/VectorCallActivity.kt b/vector/src/main/java/im/vector/app/features/call/VectorCallActivity.kt index e9d16ee710..a904658e9c 100644 --- a/vector/src/main/java/im/vector/app/features/call/VectorCallActivity.kt +++ b/vector/src/main/java/im/vector/app/features/call/VectorCallActivity.kt @@ -24,6 +24,8 @@ import android.content.Intent import android.content.Intent.FLAG_ACTIVITY_CLEAR_TOP import android.content.res.Configuration import android.graphics.Color +import android.media.projection.MediaProjection +import android.media.projection.MediaProjectionManager import android.os.Build import android.os.Bundle import android.os.Parcelable @@ -31,6 +33,7 @@ import android.util.Rational import android.view.MenuItem import android.view.View import android.view.WindowManager +import androidx.activity.result.ActivityResult import androidx.annotation.StringRes import androidx.core.content.ContextCompat import androidx.core.content.getSystemService @@ -56,6 +59,8 @@ import im.vector.app.features.call.dialpad.CallDialPadBottomSheet import im.vector.app.features.call.dialpad.DialPadFragment import im.vector.app.features.call.transfer.CallTransferActivity import im.vector.app.features.call.utils.EglUtils +import im.vector.app.features.call.webrtc.ScreenCaptureService +import im.vector.app.features.call.webrtc.ScreenCaptureServiceConnection import im.vector.app.features.call.webrtc.WebRtcCall import im.vector.app.features.call.webrtc.WebRtcCallManager import im.vector.app.features.displayname.getBestName @@ -73,6 +78,7 @@ import org.matrix.android.sdk.api.session.call.TurnServerResponse import org.matrix.android.sdk.api.session.room.model.call.EndCallReason import org.webrtc.EglBase import org.webrtc.RendererCommon +import org.webrtc.ScreenCapturerAndroid import timber.log.Timber import javax.inject.Inject @@ -94,6 +100,7 @@ class VectorCallActivity : VectorBaseActivity(), CallContro @Inject lateinit var callManager: WebRtcCallManager @Inject lateinit var avatarRenderer: AvatarRenderer + @Inject lateinit var screenCaptureServiceConnection: ScreenCaptureServiceConnection private val callViewModel: VectorCallViewModel by viewModel() @@ -157,6 +164,9 @@ class VectorCallActivity : VectorBaseActivity(), CallContro } } } + + // Bind to service in case of user killed the app while there is an ongoing call + bindToScreenCaptureService() } override fun onNewIntent(intent: Intent?) { @@ -512,20 +522,22 @@ class VectorCallActivity : VectorBaseActivity(), CallContro private fun handleViewEvents(event: VectorCallViewEvents?) { Timber.tag(loggerTag.value).v("handleViewEvents $event") when (event) { - is VectorCallViewEvents.ConnectionTimeout -> { + is VectorCallViewEvents.ConnectionTimeout -> { onErrorTimoutConnect(event.turn) } - is VectorCallViewEvents.ShowDialPad -> { + is VectorCallViewEvents.ShowDialPad -> { CallDialPadBottomSheet.newInstance(false).apply { callback = dialPadCallback }.show(supportFragmentManager, FRAGMENT_DIAL_PAD_TAG) } - is VectorCallViewEvents.ShowCallTransferScreen -> { + is VectorCallViewEvents.ShowCallTransferScreen -> { val callId = withState(callViewModel) { it.callId } navigator.openCallTransfer(this, callTransferActivityResultLauncher, callId) } - is VectorCallViewEvents.FailToTransfer -> showSnackbar(getString(R.string.call_transfer_failure)) - else -> Unit + is VectorCallViewEvents.FailToTransfer -> showSnackbar(getString(R.string.call_transfer_failure)) + is VectorCallViewEvents.ShowScreenSharingPermissionDialog -> handleShowScreenSharingPermissionDialog() + is VectorCallViewEvents.StopScreenSharingService -> handleStopScreenSharingService() + else -> Unit } } @@ -628,6 +640,54 @@ class VectorCallActivity : VectorBaseActivity(), CallContro } } + private val screenSharingPermissionActivityResultLauncher = registerStartForActivityResult { activityResult -> + if (activityResult.resultCode == Activity.RESULT_OK) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { + // We need to start a foreground service with a sticky notification during screen sharing + startScreenSharingService(activityResult) + } else { + startScreenSharing(activityResult) + } + } + } + + private fun startScreenSharing(activityResult: ActivityResult) { + val videoCapturer = ScreenCapturerAndroid(activityResult.data, object : MediaProjection.Callback() { + override fun onStop() { + Timber.i("User revoked the screen capturing permission") + } + }) + callViewModel.handle(VectorCallViewActions.StartScreenSharing(videoCapturer)) + } + + private fun startScreenSharingService(activityResult: ActivityResult) { + ContextCompat.startForegroundService( + this, + Intent(this, ScreenCaptureService::class.java) + ) + bindToScreenCaptureService(activityResult) + } + + private fun bindToScreenCaptureService(activityResult: ActivityResult? = null) { + screenCaptureServiceConnection.bind(object : ScreenCaptureServiceConnection.Callback { + override fun onServiceConnected() { + activityResult?.let { startScreenSharing(it) } + } + }) + } + + private fun handleShowScreenSharingPermissionDialog() { + getSystemService()?.let { + navigator.openScreenSharingPermissionDialog(it.createScreenCaptureIntent(), screenSharingPermissionActivityResultLauncher) + } + } + + private fun handleStopScreenSharingService() { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { + screenCaptureServiceConnection.stopScreenCapturing() + } + } + companion object { private const val EXTRA_MODE = "EXTRA_MODE" private const val FRAGMENT_DIAL_PAD_TAG = "FRAGMENT_DIAL_PAD_TAG" diff --git a/vector/src/main/java/im/vector/app/features/call/CallSessionDependencies.kt b/vector/src/main/java/im/vector/app/features/call/VectorCallService.kt similarity index 100% rename from vector/src/main/java/im/vector/app/features/call/CallSessionDependencies.kt rename to vector/src/main/java/im/vector/app/features/call/VectorCallService.kt diff --git a/vector/src/main/java/im/vector/app/features/call/VectorCallViewActions.kt b/vector/src/main/java/im/vector/app/features/call/VectorCallViewActions.kt index d1ed961814..cec118f296 100644 --- a/vector/src/main/java/im/vector/app/features/call/VectorCallViewActions.kt +++ b/vector/src/main/java/im/vector/app/features/call/VectorCallViewActions.kt @@ -19,6 +19,7 @@ package im.vector.app.features.call import im.vector.app.core.platform.VectorViewModelAction import im.vector.app.features.call.audio.CallAudioManager import im.vector.app.features.call.transfer.CallTransferResult +import org.webrtc.VideoCapturer sealed class VectorCallViewActions : VectorViewModelAction { object EndCall : VectorCallViewActions() @@ -40,4 +41,6 @@ sealed class VectorCallViewActions : VectorViewModelAction { object CallTransferSelectionCancelled : VectorCallViewActions() data class CallTransferSelectionResult(val callTransferResult: CallTransferResult) : VectorCallViewActions() object TransferCall : VectorCallViewActions() + object ToggleScreenSharing : VectorCallViewActions() + data class StartScreenSharing(val videoCapturer: VideoCapturer) : VectorCallViewActions() } diff --git a/vector/src/main/java/im/vector/app/features/call/VectorCallViewEvents.kt b/vector/src/main/java/im/vector/app/features/call/VectorCallViewEvents.kt index 7c29d7eea3..52510f6f8b 100644 --- a/vector/src/main/java/im/vector/app/features/call/VectorCallViewEvents.kt +++ b/vector/src/main/java/im/vector/app/features/call/VectorCallViewEvents.kt @@ -27,10 +27,10 @@ sealed class VectorCallViewEvents : VectorViewEvents { val available: Set, val current: CallAudioManager.Device ) : VectorCallViewEvents() + object ShowDialPad : VectorCallViewEvents() object ShowCallTransferScreen : VectorCallViewEvents() object FailToTransfer : VectorCallViewEvents() -// data class CallAnswered(val content: CallAnswerContent) : VectorCallViewEvents() -// data class CallHangup(val content: CallHangupContent) : VectorCallViewEvents() -// object CallAccepted : VectorCallViewEvents() + object ShowScreenSharingPermissionDialog : VectorCallViewEvents() + object StopScreenSharingService : VectorCallViewEvents() } diff --git a/vector/src/main/java/im/vector/app/features/call/VectorCallViewModel.kt b/vector/src/main/java/im/vector/app/features/call/VectorCallViewModel.kt index b9bf578daa..e2cedbe1b0 100644 --- a/vector/src/main/java/im/vector/app/features/call/VectorCallViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/call/VectorCallViewModel.kt @@ -42,6 +42,7 @@ import org.matrix.android.sdk.api.session.Session import org.matrix.android.sdk.api.session.call.CallState import org.matrix.android.sdk.api.session.call.MxCall import org.matrix.android.sdk.api.session.call.MxPeerConnectionState +import org.matrix.android.sdk.api.session.getRoomSummary import org.matrix.android.sdk.api.session.room.model.call.supportCallTransfer import org.matrix.android.sdk.api.util.MatrixItem @@ -144,9 +145,10 @@ class VectorCallViewModel @AssistedInject constructor( override fun onCallEnded(callId: String) { withState { state -> if (state.otherKnownCallInfo?.callId == callId) { - setState { copy(otherKnownCallInfo = null) } + setState { copy(otherKnownCallInfo = null, isSharingScreen = false) } } } + _viewEvents.post(VectorCallViewEvents.StopScreenSharingService) } override fun onCurrentCallChange(call: WebRtcCall?) { @@ -155,9 +157,10 @@ class VectorCallViewModel @AssistedInject constructor( } } - override fun onAudioDevicesChange() { - val currentSoundDevice = callManager.audioManager.selectedDevice ?: return - if (currentSoundDevice == CallAudioManager.Device.Phone) { + override fun onAudioDevicesChange() = withState { state -> + val currentSoundDevice = callManager.audioManager.selectedDevice ?: return@withState + val webRtcCall = callManager.getCallById(state.callId) + if (webRtcCall != null && shouldActivateProximitySensor(webRtcCall)) { proximityManager.start() } else { proximityManager.stop() @@ -204,7 +207,7 @@ class VectorCallViewModel @AssistedInject constructor( callManager.addListener(callManagerListener) webRtcCall.addListener(callListener) val currentSoundDevice = callManager.audioManager.selectedDevice - if (currentSoundDevice == CallAudioManager.Device.Phone) { + if (shouldActivateProximitySensor(webRtcCall)) { proximityManager.start() } setState { @@ -223,13 +226,18 @@ class VectorCallViewModel @AssistedInject constructor( formattedDuration = webRtcCall.formattedDuration(), isHD = webRtcCall.mxCall.isVideoCall && webRtcCall.currentCaptureFormat() is CaptureFormat.HD, canOpponentBeTransferred = webRtcCall.mxCall.capabilities.supportCallTransfer(), - transferee = computeTransfereeState(webRtcCall.mxCall) + transferee = computeTransfereeState(webRtcCall.mxCall), + isSharingScreen = webRtcCall.isSharingScreen() ) } updateOtherKnownCall(webRtcCall) } } + private fun shouldActivateProximitySensor(webRtcCall: WebRtcCall): Boolean { + return callManager.audioManager.selectedDevice == CallAudioManager.Device.Phone && !webRtcCall.isSharingScreen() + } + private fun WebRtcCall.extractCallInfo(): VectorCallViewState.CallInfo { val assertedIdentity = this.remoteAssertedIdentity val matrixItem = if (assertedIdentity != null) { @@ -256,27 +264,30 @@ class VectorCallViewModel @AssistedInject constructor( override fun handle(action: VectorCallViewActions) = withState { state -> when (action) { - VectorCallViewActions.EndCall -> call?.endCall() - VectorCallViewActions.AcceptCall -> { + VectorCallViewActions.EndCall -> { + call?.endCall() + _viewEvents.post(VectorCallViewEvents.StopScreenSharingService) + } + VectorCallViewActions.AcceptCall -> { setState { copy(callState = Loading()) } call?.acceptIncomingCall() } - VectorCallViewActions.DeclineCall -> { + VectorCallViewActions.DeclineCall -> { setState { copy(callState = Loading()) } call?.endCall() } - VectorCallViewActions.ToggleMute -> { + VectorCallViewActions.ToggleMute -> { val muted = state.isAudioMuted call?.muteCall(!muted) setState { copy(isAudioMuted = !muted) } } - VectorCallViewActions.ToggleVideo -> { + VectorCallViewActions.ToggleVideo -> { if (state.isVideoCall) { val videoEnabled = state.isVideoEnabled call?.enableVideo(!videoEnabled) @@ -286,19 +297,19 @@ class VectorCallViewModel @AssistedInject constructor( } Unit } - VectorCallViewActions.ToggleHoldResume -> { + VectorCallViewActions.ToggleHoldResume -> { val isRemoteOnHold = state.isRemoteOnHold call?.updateRemoteOnHold(!isRemoteOnHold) } - is VectorCallViewActions.ChangeAudioDevice -> { + is VectorCallViewActions.ChangeAudioDevice -> { callManager.audioManager.setAudioDevice(action.device) } - VectorCallViewActions.SwitchSoundDevice -> { + VectorCallViewActions.SwitchSoundDevice -> { _viewEvents.post( VectorCallViewEvents.ShowSoundDeviceChooser(state.availableDevices, state.device) ) } - VectorCallViewActions.HeadSetButtonPressed -> { + VectorCallViewActions.HeadSetButtonPressed -> { if (state.callState.invoke() is CallState.LocalRinging) { // accept call call?.acceptIncomingCall() @@ -309,20 +320,20 @@ class VectorCallViewModel @AssistedInject constructor( } Unit } - VectorCallViewActions.ToggleCamera -> { + VectorCallViewActions.ToggleCamera -> { call?.switchCamera() } - VectorCallViewActions.ToggleHDSD -> { + VectorCallViewActions.ToggleHDSD -> { if (!state.isVideoCall) return@withState call?.setCaptureFormat(if (state.isHD) CaptureFormat.SD else CaptureFormat.HD) } - VectorCallViewActions.OpenDialPad -> { + VectorCallViewActions.OpenDialPad -> { _viewEvents.post(VectorCallViewEvents.ShowDialPad) } - is VectorCallViewActions.SendDtmfDigit -> { + is VectorCallViewActions.SendDtmfDigit -> { call?.sendDtmfDigit(action.digit) } - VectorCallViewActions.InitiateCallTransfer -> { + VectorCallViewActions.InitiateCallTransfer -> { call?.updateRemoteOnHold(true) _viewEvents.post( VectorCallViewEvents.ShowCallTransferScreen @@ -334,13 +345,42 @@ class VectorCallViewModel @AssistedInject constructor( is VectorCallViewActions.CallTransferSelectionResult -> { handleCallTransferSelectionResult(action.callTransferResult) } - VectorCallViewActions.TransferCall -> { + VectorCallViewActions.TransferCall -> { handleCallTransfer() } - is VectorCallViewActions.SwitchCall -> { + is VectorCallViewActions.SwitchCall -> { setState { VectorCallViewState(action.callArgs) } setupCallWithCurrentState() } + is VectorCallViewActions.ToggleScreenSharing -> { + handleToggleScreenSharing(state.isSharingScreen) + } + is VectorCallViewActions.StartScreenSharing -> { + call?.startSharingScreen(action.videoCapturer) + proximityManager.stop() + setState { + copy(isSharingScreen = true) + } + } + } + } + + private fun handleToggleScreenSharing(isSharingScreen: Boolean) { + if (isSharingScreen) { + call?.stopSharingScreen() + setState { + copy(isSharingScreen = false) + } + _viewEvents.post( + VectorCallViewEvents.StopScreenSharingService + ) + if (callManager.audioManager.selectedDevice == CallAudioManager.Device.Phone) { + proximityManager.start() + } + } else { + _viewEvents.post( + VectorCallViewEvents.ShowScreenSharingPermissionDialog + ) } } diff --git a/vector/src/main/java/im/vector/app/features/call/VectorCallViewState.kt b/vector/src/main/java/im/vector/app/features/call/VectorCallViewState.kt index 2d33cbf9b9..2cd819b5f5 100644 --- a/vector/src/main/java/im/vector/app/features/call/VectorCallViewState.kt +++ b/vector/src/main/java/im/vector/app/features/call/VectorCallViewState.kt @@ -42,7 +42,8 @@ data class VectorCallViewState( val callInfo: CallInfo? = null, val formattedDuration: String = "", val canOpponentBeTransferred: Boolean = false, - val transferee: TransfereeState = TransfereeState.NoTransferee + val transferee: TransfereeState = TransfereeState.NoTransferee, + val isSharingScreen: Boolean = false ) : MavericksState { sealed class TransfereeState { diff --git a/vector/src/main/java/im/vector/app/features/call/audio/API23AudioDeviceDetector.kt b/vector/src/main/java/im/vector/app/features/call/audio/API23AudioDeviceDetector.kt index 7e12987a6b..ccf79cc02d 100644 --- a/vector/src/main/java/im/vector/app/features/call/audio/API23AudioDeviceDetector.kt +++ b/vector/src/main/java/im/vector/app/features/call/audio/API23AudioDeviceDetector.kt @@ -32,10 +32,11 @@ internal class API23AudioDeviceDetector(private val audioManager: AudioManager, val deviceInfos = audioManager.getDevices(AudioManager.GET_DEVICES_OUTPUTS) for (info in deviceInfos) { when (info.type) { - AudioDeviceInfo.TYPE_BLUETOOTH_SCO -> devices.add(CallAudioManager.Device.WirelessHeadset(info.productName.toString())) - AudioDeviceInfo.TYPE_BUILTIN_EARPIECE -> devices.add(CallAudioManager.Device.Phone) - AudioDeviceInfo.TYPE_BUILTIN_SPEAKER -> devices.add(CallAudioManager.Device.Speaker) - AudioDeviceInfo.TYPE_WIRED_HEADPHONES, AudioDeviceInfo.TYPE_WIRED_HEADSET, TYPE_USB_HEADSET -> devices.add(CallAudioManager.Device.Headset) + AudioDeviceInfo.TYPE_BLUETOOTH_SCO -> devices.add(CallAudioManager.Device.WirelessHeadset(info.productName.toString())) + AudioDeviceInfo.TYPE_BUILTIN_EARPIECE -> devices.add(CallAudioManager.Device.Phone) + AudioDeviceInfo.TYPE_BUILTIN_SPEAKER -> devices.add(CallAudioManager.Device.Speaker) + AudioDeviceInfo.TYPE_WIRED_HEADPHONES, + AudioDeviceInfo.TYPE_WIRED_HEADSET, TYPE_USB_HEADSET -> devices.add(CallAudioManager.Device.Headset) } } callAudioManager.replaceDevices(devices) diff --git a/vector/src/main/java/im/vector/app/features/call/audio/DefaultAudioDeviceRouter.kt b/vector/src/main/java/im/vector/app/features/call/audio/DefaultAudioDeviceRouter.kt index fd85ce075f..c3500c7bc2 100644 --- a/vector/src/main/java/im/vector/app/features/call/audio/DefaultAudioDeviceRouter.kt +++ b/vector/src/main/java/im/vector/app/features/call/audio/DefaultAudioDeviceRouter.kt @@ -95,7 +95,7 @@ class DefaultAudioDeviceRouter(private val audioManager: AudioManager, override fun onAudioFocusChange(focusChange: Int) { callAudioManager.runInAudioThread { when (focusChange) { - AudioManager.AUDIOFOCUS_GAIN -> { + AudioManager.AUDIOFOCUS_GAIN -> { Timber.d(" Audio focus gained") if (audioFocusLost) { callAudioManager.resetAudioRoute() diff --git a/vector/src/main/java/im/vector/app/features/call/conference/JitsiService.kt b/vector/src/main/java/im/vector/app/features/call/conference/JitsiService.kt index 6fa65bdb7f..b0da4f0e18 100644 --- a/vector/src/main/java/im/vector/app/features/call/conference/JitsiService.kt +++ b/vector/src/main/java/im/vector/app/features/call/conference/JitsiService.kt @@ -19,6 +19,7 @@ package im.vector.app.features.call.conference import im.vector.app.R import im.vector.app.core.network.await import im.vector.app.core.resources.StringProvider +import im.vector.app.core.time.Clock import im.vector.app.core.utils.ensureProtocol import im.vector.app.core.utils.toBase32String import im.vector.app.features.call.conference.jwt.JitsiJWTFactory @@ -31,6 +32,7 @@ import org.jitsi.meet.sdk.JitsiMeetUserInfo import org.matrix.android.sdk.api.extensions.tryOrNull import org.matrix.android.sdk.api.raw.RawService import org.matrix.android.sdk.api.session.Session +import org.matrix.android.sdk.api.session.getRoomSummary import org.matrix.android.sdk.api.session.widgets.model.Widget import org.matrix.android.sdk.api.session.widgets.model.WidgetType import org.matrix.android.sdk.api.util.MatrixJsonParser @@ -45,7 +47,9 @@ class JitsiService @Inject constructor( private val rawService: RawService, private val stringProvider: StringProvider, private val themeProvider: ThemeProvider, - private val jitsiJWTFactory: JitsiJWTFactory) { + private val jitsiJWTFactory: JitsiJWTFactory, + private val clock: Clock, +) { companion object { const val JITSI_OPEN_ID_TOKEN_JWT_AUTH = "openidtoken-jwt" @@ -59,7 +63,7 @@ class JitsiService @Inject constructor( suspend fun createJitsiWidget(roomId: String, withVideo: Boolean): Widget { // Build data for a jitsi widget - val widgetId: String = WidgetType.Jitsi.preferred + "_" + session.myUserId + "_" + System.currentTimeMillis() + val widgetId: String = WidgetType.Jitsi.preferred + "_" + session.myUserId + "_" + clock.epochMillis() val preferredJitsiDomain = tryOrNull { rawService.getElementWellknown(session.sessionParams) ?.jitsiServer @@ -99,7 +103,7 @@ class JitsiService @Inject constructor( } suspend fun joinConference(roomId: String, jitsiWidget: Widget, enableVideo: Boolean): JitsiCallViewEvents.JoinConference { - val me = session.getRoomMember(session.myUserId, roomId)?.toMatrixItem() + val me = session.roomService().getRoomMember(session.myUserId, roomId)?.toMatrixItem() val userDisplayName = me?.getBestName() val userAvatar = me?.avatarUrl?.let { session.contentUrlResolver().resolveFullSize(it) } val userInfo = JitsiMeetUserInfo().apply { diff --git a/vector/src/main/java/im/vector/app/features/call/dialpad/DialPadLookup.kt b/vector/src/main/java/im/vector/app/features/call/dialpad/DialPadLookup.kt index eadccab4f6..e835a74fd6 100644 --- a/vector/src/main/java/im/vector/app/features/call/dialpad/DialPadLookup.kt +++ b/vector/src/main/java/im/vector/app/features/call/dialpad/DialPadLookup.kt @@ -47,9 +47,9 @@ class DialPadLookup @Inject constructor( if (nativeUserId == session.myUserId) { throw Failure.NumberIsYours } - session.getExistingDirectRoomWithUser(nativeUserId) + session.roomService().getExistingDirectRoomWithUser(nativeUserId) // if there is not, just create a DM with the sip user - ?: directRoomHelper.ensureDMExists(sipUserId) + ?: directRoomHelper.ensureDMExists(sipUserId) } else { // do the same if there is no corresponding native user. directRoomHelper.ensureDMExists(sipUserId) diff --git a/vector/src/main/java/im/vector/app/features/call/lookup/CallUserMapper.kt b/vector/src/main/java/im/vector/app/features/call/lookup/CallUserMapper.kt index 0820b34124..eb04ab63d7 100644 --- a/vector/src/main/java/im/vector/app/features/call/lookup/CallUserMapper.kt +++ b/vector/src/main/java/im/vector/app/features/call/lookup/CallUserMapper.kt @@ -20,6 +20,7 @@ import org.matrix.android.sdk.api.extensions.tryOrNull import org.matrix.android.sdk.api.session.Session import org.matrix.android.sdk.api.session.events.model.toContent import org.matrix.android.sdk.api.session.events.model.toModel +import org.matrix.android.sdk.api.session.getRoom import org.matrix.android.sdk.api.session.room.Room import org.matrix.android.sdk.api.session.room.accountdata.RoomAccountDataTypes import org.matrix.android.sdk.api.session.room.model.create.CreateRoomParams @@ -29,7 +30,7 @@ class CallUserMapper(private val session: Session, private val protocolsChecker: fun nativeRoomForVirtualRoom(roomId: String): String? { if (!protocolsChecker.supportVirtualRooms) return null val virtualRoom = session.getRoom(roomId) ?: return null - val virtualRoomEvent = virtualRoom.getAccountDataEvent(RoomAccountDataTypes.EVENT_TYPE_VIRTUAL_ROOM) + val virtualRoomEvent = virtualRoom.roomAccountDataService().getAccountDataEvent(RoomAccountDataTypes.EVENT_TYPE_VIRTUAL_ROOM) return virtualRoomEvent?.content?.toModel()?.nativeRoomId } @@ -61,7 +62,7 @@ class CallUserMapper(private val session: Session, private val protocolsChecker: val nativeLookup = session.sipNativeLookup(inviterId).firstOrNull() ?: return if (nativeLookup.fields.containsKey("is_virtual")) { val nativeUser = nativeLookup.userId - val nativeRoomId = session.getExistingDirectRoomWithUser(nativeUser) + val nativeRoomId = session.roomService().getExistingDirectRoomWithUser(nativeUser) if (nativeRoomId != null) { // It's a virtual room with a matching native room, so set the room account data. This // will make sure we know where how to map calls and also allow us know not to display @@ -78,11 +79,11 @@ class CallUserMapper(private val session: Session, private val protocolsChecker: private suspend fun Room.markVirtual(nativeRoomId: String) { val virtualRoomContent = RoomVirtualContent(nativeRoomId = nativeRoomId) - updateAccountData(RoomAccountDataTypes.EVENT_TYPE_VIRTUAL_ROOM, virtualRoomContent.toContent()) + roomAccountDataService().updateAccountData(RoomAccountDataTypes.EVENT_TYPE_VIRTUAL_ROOM, virtualRoomContent.toContent()) } private suspend fun ensureVirtualRoomExists(userId: String, nativeRoomId: String): String { - val existingDMRoom = tryOrNull { session.getExistingDirectRoomWithUser(userId) } + val existingDMRoom = tryOrNull { session.roomService().getExistingDirectRoomWithUser(userId) } val roomId: String if (existingDMRoom != null) { roomId = existingDMRoom @@ -92,7 +93,7 @@ class CallUserMapper(private val session: Session, private val protocolsChecker: setDirectMessage() creationContent[RoomAccountDataTypes.EVENT_TYPE_VIRTUAL_ROOM] = nativeRoomId } - roomId = session.createRoom(roomParams) + roomId = session.roomService().createRoom(roomParams) } return roomId } diff --git a/vector/src/main/java/im/vector/app/features/call/telecom/VectorConnectionService.kt b/vector/src/main/java/im/vector/app/features/call/telecom/VectorConnectionService.kt index e289537177..4a630dc451 100644 --- a/vector/src/main/java/im/vector/app/features/call/telecom/VectorConnectionService.kt +++ b/vector/src/main/java/im/vector/app/features/call/telecom/VectorConnectionService.kt @@ -50,7 +50,7 @@ import im.vector.app.core.services.CallService @RequiresApi(Build.VERSION_CODES.M) class VectorConnectionService : ConnectionService() { /** - * The telecom subsystem calls this method in response to your app calling placeCall(Uri, Bundle) to create a new outgoing call + * The telecom subsystem calls this method in response to your app calling placeCall(Uri, Bundle) to create a new outgoing call. */ override fun onCreateOutgoingConnection(connectionManagerPhoneAccount: PhoneAccountHandle?, request: ConnectionRequest?): Connection? { val callId = request?.address?.encodedQuery ?: return null diff --git a/vector/src/main/java/im/vector/app/features/call/transfer/CallTransferViewModel.kt b/vector/src/main/java/im/vector/app/features/call/transfer/CallTransferViewModel.kt index 1765b58a02..67f0949dcf 100644 --- a/vector/src/main/java/im/vector/app/features/call/transfer/CallTransferViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/call/transfer/CallTransferViewModel.kt @@ -31,7 +31,7 @@ import org.matrix.android.sdk.api.session.call.MxCall class CallTransferViewModel @AssistedInject constructor(@Assisted initialState: CallTransferViewState, private val callManager: WebRtcCallManager) : - VectorViewModel(initialState) { + VectorViewModel(initialState) { @AssistedFactory interface Factory : MavericksAssistedViewModelFactory { @@ -62,5 +62,5 @@ class CallTransferViewModel @AssistedInject constructor(@Assisted initialState: call?.removeListener(callListener) } - override fun handle(action: EmptyAction) { } + override fun handle(action: EmptyAction) {} } diff --git a/vector/src/main/java/im/vector/app/features/call/webrtc/PeerConnectionObserver.kt b/vector/src/main/java/im/vector/app/features/call/webrtc/PeerConnectionObserver.kt index 99c26c5ebe..c776951f93 100644 --- a/vector/src/main/java/im/vector/app/features/call/webrtc/PeerConnectionObserver.kt +++ b/vector/src/main/java/im/vector/app/features/call/webrtc/PeerConnectionObserver.kt @@ -37,13 +37,13 @@ class PeerConnectionObserver(private val webRtcCall: WebRtcCall) : PeerConnectio * Every ICE transport used by the connection is either in use (state "connected" or "completed") * or is closed (state "closed"); in addition, at least one transport is either "connected" or "completed" */ - PeerConnection.PeerConnectionState.CONNECTED -> { + PeerConnection.PeerConnectionState.CONNECTED -> { webRtcCall.mxCall.state = CallState.Connected(MxPeerConnectionState.CONNECTED) } /** * One or more of the ICE transports on the connection is in the "failed" state. */ - PeerConnection.PeerConnectionState.FAILED -> { + PeerConnection.PeerConnectionState.FAILED -> { // This can be temporary, e.g when other ice not yet received... // webRtcCall.mxCall.state = CallState.ERROR webRtcCall.mxCall.state = CallState.Connected(MxPeerConnectionState.FAILED) @@ -58,7 +58,7 @@ class PeerConnectionObserver(private val webRtcCall: WebRtcCall) : PeerConnectio * One or more of the ICE transports are currently in the process of establishing a connection; * that is, their RTCIceConnectionState is either "checking" or "connected", and no transports are in the "failed" state */ - PeerConnection.PeerConnectionState.CONNECTING -> { + PeerConnection.PeerConnectionState.CONNECTING -> { webRtcCall.mxCall.state = CallState.Connected(MxPeerConnectionState.CONNECTING) } /** @@ -66,7 +66,7 @@ class PeerConnectionObserver(private val webRtcCall: WebRtcCall) : PeerConnectio * This value was in the RTCSignalingState enum (and therefore found by reading the value of the signalingState) * property until the May 13, 2016 draft of the specification. */ - PeerConnection.PeerConnectionState.CLOSED -> { + PeerConnection.PeerConnectionState.CLOSED -> { webRtcCall.mxCall.state = CallState.Connected(MxPeerConnectionState.CLOSED) } /** @@ -76,7 +76,7 @@ class PeerConnectionObserver(private val webRtcCall: WebRtcCall) : PeerConnectio PeerConnection.PeerConnectionState.DISCONNECTED -> { webRtcCall.mxCall.state = CallState.Connected(MxPeerConnectionState.DISCONNECTED) } - null -> { + null -> { } } } @@ -101,14 +101,14 @@ class PeerConnectionObserver(private val webRtcCall: WebRtcCall) : PeerConnectio * the ICE agent is gathering addresses or is waiting to be given remote candidates through * calls to RTCPeerConnection.addIceCandidate() (or both). */ - PeerConnection.IceConnectionState.NEW -> { + PeerConnection.IceConnectionState.NEW -> { } /** * The ICE agent has been given one or more remote candidates and is checking pairs of local and remote candidates * against one another to try to find a compatible match, but has not yet found a pair which will allow * the peer connection to be made. It's possible that gathering of candidates is also still underway. */ - PeerConnection.IceConnectionState.CHECKING -> { + PeerConnection.IceConnectionState.CHECKING -> { } /** @@ -117,7 +117,7 @@ class PeerConnectionObserver(private val webRtcCall: WebRtcCall) : PeerConnectio * It's possible that gathering is still underway, and it's also possible that the ICE agent is still checking * candidates against one another looking for a better connection to use. */ - PeerConnection.IceConnectionState.CONNECTED -> { + PeerConnection.IceConnectionState.CONNECTED -> { } /** * Checks to ensure that components are still connected failed for at least one component of the RTCPeerConnection. @@ -131,18 +131,18 @@ class PeerConnectionObserver(private val webRtcCall: WebRtcCall) : PeerConnectio * compatible matches for all components of the connection. * It is, however, possible that the ICE agent did find compatible connections for some components. */ - PeerConnection.IceConnectionState.FAILED -> { + PeerConnection.IceConnectionState.FAILED -> { webRtcCall.onRenegotiationNeeded(restartIce = true) } /** * The ICE agent has finished gathering candidates, has checked all pairs against one another, and has found a connection for all components. */ - PeerConnection.IceConnectionState.COMPLETED -> { + PeerConnection.IceConnectionState.COMPLETED -> { } /** * The ICE agent for this RTCPeerConnection has shut down and is no longer handling requests. */ - PeerConnection.IceConnectionState.CLOSED -> { + PeerConnection.IceConnectionState.CLOSED -> { } } } diff --git a/vector/src/main/java/im/vector/app/features/call/webrtc/ScreenCaptureService.kt b/vector/src/main/java/im/vector/app/features/call/webrtc/ScreenCaptureService.kt new file mode 100644 index 0000000000..489b2d1eae --- /dev/null +++ b/vector/src/main/java/im/vector/app/features/call/webrtc/ScreenCaptureService.kt @@ -0,0 +1,58 @@ +/* + * 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.call.webrtc + +import android.content.Intent +import android.os.Binder +import android.os.IBinder +import dagger.hilt.android.AndroidEntryPoint +import im.vector.app.core.services.VectorService +import im.vector.app.core.time.Clock +import im.vector.app.features.notifications.NotificationUtils +import javax.inject.Inject + +@AndroidEntryPoint +class ScreenCaptureService : VectorService() { + + @Inject lateinit var notificationUtils: NotificationUtils + @Inject lateinit var clock: Clock + private val binder = LocalBinder() + + override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int { + showStickyNotification() + + return START_STICKY + } + + private fun showStickyNotification() { + val notificationId = clock.epochMillis().toInt() + val notification = notificationUtils.buildScreenSharingNotification() + startForeground(notificationId, notification) + } + + override fun onBind(intent: Intent?): IBinder { + return binder + } + + fun stopService() { + stopSelf() + } + + inner class LocalBinder : Binder() { + fun getService(): ScreenCaptureService = this@ScreenCaptureService + } +} diff --git a/vector/src/main/java/im/vector/app/features/call/webrtc/ScreenCaptureServiceConnection.kt b/vector/src/main/java/im/vector/app/features/call/webrtc/ScreenCaptureServiceConnection.kt new file mode 100644 index 0000000000..aa7c7f450a --- /dev/null +++ b/vector/src/main/java/im/vector/app/features/call/webrtc/ScreenCaptureServiceConnection.kt @@ -0,0 +1,65 @@ +/* + * 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.call.webrtc + +import android.content.ComponentName +import android.content.Context +import android.content.Intent +import android.content.ServiceConnection +import android.os.IBinder +import javax.inject.Inject + +class ScreenCaptureServiceConnection @Inject constructor( + private val context: Context +) : ServiceConnection { + + interface Callback { + fun onServiceConnected() + } + + private var isBound = false + private var screenCaptureService: ScreenCaptureService? = null + private var callback: Callback? = null + + fun bind(callback: Callback) { + this.callback = callback + + if (isBound) { + callback.onServiceConnected() + } else { + Intent(context, ScreenCaptureService::class.java).also { intent -> + context.bindService(intent, this, 0) + } + } + } + + fun stopScreenCapturing() { + screenCaptureService?.stopService() + } + + override fun onServiceConnected(className: ComponentName, binder: IBinder) { + screenCaptureService = (binder as ScreenCaptureService.LocalBinder).getService() + isBound = true + callback?.onServiceConnected() + } + + override fun onServiceDisconnected(className: ComponentName) { + isBound = false + screenCaptureService = null + callback = null + } +} diff --git a/vector/src/main/java/im/vector/app/features/call/webrtc/WebRtcCall.kt b/vector/src/main/java/im/vector/app/features/call/webrtc/WebRtcCall.kt index 90088c8475..b92316c292 100644 --- a/vector/src/main/java/im/vector/app/features/call/webrtc/WebRtcCall.kt +++ b/vector/src/main/java/im/vector/app/features/call/webrtc/WebRtcCall.kt @@ -80,10 +80,12 @@ import org.webrtc.MediaConstraints import org.webrtc.MediaStream import org.webrtc.PeerConnection import org.webrtc.PeerConnectionFactory +import org.webrtc.RtpSender import org.webrtc.RtpTransceiver import org.webrtc.SessionDescription import org.webrtc.SurfaceTextureHelper import org.webrtc.SurfaceViewRenderer +import org.webrtc.VideoCapturer import org.webrtc.VideoSource import org.webrtc.VideoTrack import timber.log.Timber @@ -95,6 +97,7 @@ import kotlin.coroutines.CoroutineContext private const val STREAM_ID = "userMedia" private const val AUDIO_TRACK_ID = "${STREAM_ID}a0" private const val VIDEO_TRACK_ID = "${STREAM_ID}v0" +private const val SCREEN_TRACK_ID = "${STREAM_ID}s0" private val DEFAULT_AUDIO_CONSTRAINTS = MediaConstraints() private const val INVITE_TIMEOUT_IN_MS = 60_000L @@ -153,13 +156,16 @@ class WebRtcCall( private var makingOffer: Boolean = false private var ignoreOffer: Boolean = false - private var videoCapturer: CameraVideoCapturer? = null + private var videoCapturer: VideoCapturer? = null private val availableCamera = ArrayList() private var cameraInUse: CameraProxy? = null private var currentCaptureFormat: CaptureFormat = CaptureFormat.HD private var cameraAvailabilityCallback: CameraManager.AvailabilityCallback? = null + private var videoSender: RtpSender? = null + private var screenSender: RtpSender? = null + private val timer = CountUpTimer(1000L).apply { tickListener = object : CountUpTimer.TickListener { override fun onTick(milliseconds: Long) { @@ -304,7 +310,7 @@ class WebRtcCall( } /** - * Without consultation + * Without consultation. */ fun transferToUser(targetUserId: String, targetRoomId: String?) { sessionScope?.launch(dispatcher) { @@ -319,7 +325,7 @@ class WebRtcCall( } /** - * With consultation + * With consultation. */ fun transferToCall(transferTargetCall: WebRtcCall) { sessionScope?.launch(dispatcher) { @@ -351,7 +357,7 @@ class WebRtcCall( } /** - * Sends a DTMF digit to the other party + * Sends a DTMF digit to the other party. * @param digit The digit (nb. string - '#' and '*' are dtmf too) */ fun sendDtmfDigit(digit: String) { @@ -617,7 +623,7 @@ class WebRtcCall( val videoTrack = peerConnectionFactory.createVideoTrack(VIDEO_TRACK_ID, videoSource) Timber.tag(loggerTag.value).v("Add video track $VIDEO_TRACK_ID to call ${mxCall.callId}") videoTrack.setEnabled(true) - peerConnection?.addTrack(videoTrack, listOf(STREAM_ID)) + videoSender = peerConnection?.addTrack(videoTrack, listOf(STREAM_ID)) localVideoSource = videoSource localVideoTrack = videoTrack } @@ -670,17 +676,15 @@ class WebRtcCall( isRemoteOnHold = true isLocalOnHold = true direction = RtpTransceiver.RtpTransceiverDirection.SEND_ONLY - timer.pause() } else { isRemoteOnHold = false isLocalOnHold = wasLocalOnHold onCallBecomeActive(this@WebRtcCall) direction = RtpTransceiver.RtpTransceiverDirection.SEND_RECV - if (!isLocalOnHold) { - timer.resume() - } } for (transceiver in peerConnection?.transceivers ?: emptyList()) { + transceiver.sender.track()?.setEnabled(!onHold) + transceiver.receiver.track()?.setEnabled(!onHold) transceiver.direction = direction } updateMuteStatus() @@ -722,7 +726,7 @@ class WebRtcCall( Timber.tag(loggerTag.value).v("switchCamera") if (mxCall.state is CallState.Connected && mxCall.isVideoCall) { val oppositeCamera = getOppositeCameraIfAny() ?: return@launch - videoCapturer?.switchCamera( + (videoCapturer as? CameraVideoCapturer)?.switchCamera( object : CameraVideoCapturer.CameraSwitchHandler { // Invoked on success. |isFrontCamera| is true if the new camera is front facing. override fun onCameraSwitchDone(isFrontCamera: Boolean) { @@ -770,6 +774,62 @@ class WebRtcCall( return currentCaptureFormat } + fun startSharingScreen(videoCapturer: VideoCapturer) { + val factory = peerConnectionFactoryProvider.get() ?: return + + this.videoCapturer = videoCapturer + + val localMediaStream = factory.createLocalMediaStream(STREAM_ID) + val videoSource = factory.createVideoSource(videoCapturer.isScreencast) + + startCapturingScreen(videoCapturer, videoSource) + + removeLocalSurfaceRenderers() + + showScreenLocally(factory, videoSource, localMediaStream) + + videoSender?.let { removeStream(it) } + + screenSender = peerConnection?.addTrack(localVideoTrack, listOf(STREAM_ID)) + } + + fun stopSharingScreen() { + localVideoTrack?.setEnabled(false) + screenSender?.let { removeStream(it) } + if (mxCall.isVideoCall) { + peerConnectionFactoryProvider.get()?.let { configureVideoTrack(it) } + } + updateMuteStatus() + sessionScope?.launch(dispatcher) { attachViewRenderersInternal() } + } + + private fun removeStream(sender: RtpSender) { + peerConnection?.removeTrack(sender) + } + + private fun showScreenLocally(factory: PeerConnectionFactory, videoSource: VideoSource?, localMediaStream: MediaStream?) { + localVideoTrack = factory.createVideoTrack(SCREEN_TRACK_ID, videoSource).apply { setEnabled(true) } + localMediaStream?.addTrack(localVideoTrack) + localSurfaceRenderers.forEach { it.get()?.let { localVideoTrack?.addSink(it) } } + } + + private fun removeLocalSurfaceRenderers() { + localSurfaceRenderers.forEach { it.get()?.let { localVideoTrack?.removeSink(it) } } + } + + private fun startCapturingScreen(videoCapturer: VideoCapturer, videoSource: VideoSource) { + val surfaceTextureHelper = SurfaceTextureHelper.create("CaptureThread", rootEglBase!!.eglBaseContext) + videoCapturer.initialize(surfaceTextureHelper, context, videoSource.capturerObserver) + videoCapturer.startCapture(currentCaptureFormat.width, currentCaptureFormat.height, currentCaptureFormat.fps) + } + + /** + * Returns true if the user is sharing the screen, false otherwise. + */ + fun isSharingScreen(): Boolean { + return localVideoTrack?.enabled().orFalse() && localVideoTrack?.id() == SCREEN_TRACK_ID + } + private suspend fun release() { listeners.clear() mxCall.removeListener(this) @@ -933,11 +993,6 @@ class WebRtcCall( wasLocalOnHold = nowOnHold if (prevOnHold != nowOnHold) { isLocalOnHold = nowOnHold - if (nowOnHold) { - timer.pause() - } else { - timer.resume() - } listeners.forEach { tryOrNull { it.onHoldUnhold() } } @@ -981,7 +1036,7 @@ class WebRtcCall( val nativeUserId = session.sipNativeLookup(newAssertedIdentity.id!!).firstOrNull()?.userId if (nativeUserId != null) { val resolvedUser = tryOrNull { - session.resolveUser(nativeUserId) + session.userService().resolveUser(nativeUserId) } if (resolvedUser != null) { remoteAssertedIdentity = newAssertedIdentity.copy( diff --git a/vector/src/main/java/im/vector/app/features/call/webrtc/WebRtcCallExt.kt b/vector/src/main/java/im/vector/app/features/call/webrtc/WebRtcCallExt.kt index ac9d169633..de6d5acb20 100644 --- a/vector/src/main/java/im/vector/app/features/call/webrtc/WebRtcCallExt.kt +++ b/vector/src/main/java/im/vector/app/features/call/webrtc/WebRtcCallExt.kt @@ -18,6 +18,7 @@ package im.vector.app.features.call.webrtc import org.matrix.android.sdk.api.extensions.orFalse import org.matrix.android.sdk.api.session.Session +import org.matrix.android.sdk.api.session.getRoom import org.matrix.android.sdk.api.util.MatrixItem import org.matrix.android.sdk.api.util.toMatrixItem @@ -29,7 +30,7 @@ fun WebRtcCall.getOpponentAsMatrixItem(session: Session): MatrixItem? { roomSummary.toMatrixItem() } else { val userId = roomSummary.otherMemberIds.first() - return room.getRoomMember(userId)?.toMatrixItem() + return room.membershipService().getRoomMember(userId)?.toMatrixItem() } } } diff --git a/vector/src/main/java/im/vector/app/features/call/webrtc/WebRtcCallManager.kt b/vector/src/main/java/im/vector/app/features/call/webrtc/WebRtcCallManager.kt index fe12bf1ec7..5f50747d77 100644 --- a/vector/src/main/java/im/vector/app/features/call/webrtc/WebRtcCallManager.kt +++ b/vector/src/main/java/im/vector/app/features/call/webrtc/WebRtcCallManager.kt @@ -63,7 +63,7 @@ import javax.inject.Inject import javax.inject.Singleton /** - * Manage peerConnectionFactory & Peer connections outside of activity lifecycle to resist configuration changes + * Manage peerConnectionFactory & Peer connections outside of activity lifecycle to resist configuration changes. * Use app context */ private val loggerTag = LoggerTag("WebRtcCallManager", LoggerTag.VOIP) @@ -215,9 +215,10 @@ class WebRtcCallManager @Inject constructor( } Timber.tag(loggerTag.value).v("PeerConnectionFactory.initialize") - PeerConnectionFactory.initialize(PeerConnectionFactory - .InitializationOptions.builder(context.applicationContext) - .createInitializationOptions() + PeerConnectionFactory.initialize( + PeerConnectionFactory + .InitializationOptions.builder(context.applicationContext) + .createInitializationOptions() ) val options = PeerConnectionFactory.Options() @@ -226,7 +227,8 @@ class WebRtcCallManager @Inject constructor( /* enableIntelVp8Encoder */ true, /* enableH264HighProfile */ - true) + true + ) val defaultVideoDecoderFactory = DefaultVideoDecoderFactory(eglBaseContext) Timber.tag(loggerTag.value).v("PeerConnectionFactory.createPeerConnectionFactory ...") peerConnectionFactory = PeerConnectionFactory.builder() @@ -304,7 +306,8 @@ class WebRtcCallManager @Inject constructor( } CallService.onOutgoingCallRinging( context = context.applicationContext, - callId = mxCall.callId) + callId = mxCall.callId + ) // start the activity now context.startActivity(VectorCallActivity.newIntent(context, webRtcCall, VectorCallActivity.OUTGOING_CREATED)) @@ -451,7 +454,7 @@ class WebRtcCallManager @Inject constructor( } /** - * Analytics + * Analytics. */ private fun WebRtcCall.trackCallStarted() { analyticsTracker.capture( diff --git a/vector/src/main/java/im/vector/app/features/command/Command.kt b/vector/src/main/java/im/vector/app/features/command/Command.kt index 421c83c9fe..24b4ed5300 100644 --- a/vector/src/main/java/im/vector/app/features/command/Command.kt +++ b/vector/src/main/java/im/vector/app/features/command/Command.kt @@ -20,9 +20,9 @@ import androidx.annotation.StringRes import im.vector.app.R /** - * Defines the command line operations - * the user can write theses messages to perform some actions - * the list will be displayed in this order + * Defines the command line operations. + * The user can write theses messages to perform some actions. + * The list will be displayed in this order. */ enum class Command(val command: String, val aliases: Array?, diff --git a/vector/src/main/java/im/vector/app/features/command/CommandParser.kt b/vector/src/main/java/im/vector/app/features/command/CommandParser.kt index b8bef506b1..49e35687f4 100644 --- a/vector/src/main/java/im/vector/app/features/command/CommandParser.kt +++ b/vector/src/main/java/im/vector/app/features/command/CommandParser.kt @@ -411,7 +411,7 @@ class CommandParser @Inject constructor() { } /** - * Checks whether or not the current command is not supported by threads + * Checks whether or not the current command is not supported by threads. * @param slashCommand the slash command that will be checked * @param isInThreadTimeline if its true we are in a thread timeline * @return The command that is not supported diff --git a/vector/src/main/java/im/vector/app/features/command/ParsedCommand.kt b/vector/src/main/java/im/vector/app/features/command/ParsedCommand.kt index 771f721d3c..4571deb54f 100644 --- a/vector/src/main/java/im/vector/app/features/command/ParsedCommand.kt +++ b/vector/src/main/java/im/vector/app/features/command/ParsedCommand.kt @@ -20,7 +20,7 @@ import im.vector.app.features.home.room.detail.ChatEffect import org.matrix.android.sdk.api.session.identity.ThreePid /** - * Represent a parsed command + * Represent a parsed command. */ sealed interface ParsedCommand { // This is not a Slash command diff --git a/vector/src/main/java/im/vector/app/features/configuration/VectorConfiguration.kt b/vector/src/main/java/im/vector/app/features/configuration/VectorConfiguration.kt index 2c19e80772..d209adecc8 100644 --- a/vector/src/main/java/im/vector/app/features/configuration/VectorConfiguration.kt +++ b/vector/src/main/java/im/vector/app/features/configuration/VectorConfiguration.kt @@ -29,7 +29,7 @@ import java.util.Locale import javax.inject.Inject /** - * Handle locale configuration change, such as theme, font size and locale chosen by the user + * Handle locale configuration change, such as theme, font size and locale chosen by the user. */ class VectorConfiguration @Inject constructor(private val context: Context) { @@ -57,7 +57,7 @@ class VectorConfiguration @Inject constructor(private val context: Context) { } /** - * Compute a localised context + * Compute a localised context. * * @param context the context * @return the localised context @@ -100,7 +100,7 @@ class VectorConfiguration @Inject constructor(private val context: Context) { } /** - * Compute the locale status value + * Compute the locale status value. * @return the local status value */ fun getHash(): String { diff --git a/vector/src/main/java/im/vector/app/features/consent/ConsentNotGivenHelper.kt b/vector/src/main/java/im/vector/app/features/consent/ConsentNotGivenHelper.kt index 9f6aa8cdd8..463954dff7 100644 --- a/vector/src/main/java/im/vector/app/features/consent/ConsentNotGivenHelper.kt +++ b/vector/src/main/java/im/vector/app/features/consent/ConsentNotGivenHelper.kt @@ -33,7 +33,7 @@ class ConsentNotGivenHelper(private val activity: Activity, * ========================================================================================== */ /** - * Display the consent dialog, if not already displayed + * Display the consent dialog, if not already displayed. */ fun displayDialog(consentUri: String, homeServerHost: String) { dialogLocker.displayDialog { diff --git a/vector/src/main/java/im/vector/app/features/createdirect/CreateDirectRoomActivity.kt b/vector/src/main/java/im/vector/app/features/createdirect/CreateDirectRoomActivity.kt index 0053ab0fc6..25fee28123 100644 --- a/vector/src/main/java/im/vector/app/features/createdirect/CreateDirectRoomActivity.kt +++ b/vector/src/main/java/im/vector/app/features/createdirect/CreateDirectRoomActivity.kt @@ -43,6 +43,7 @@ import im.vector.app.core.utils.checkPermissions import im.vector.app.core.utils.onPermissionDeniedSnackbar import im.vector.app.core.utils.registerForPermissionsResult import im.vector.app.features.analytics.plan.MobileScreen +import im.vector.app.features.analytics.plan.ViewRoom import im.vector.app.features.contactsbook.ContactsBookFragment import im.vector.app.features.qrcode.QrCodeScannerEvents import im.vector.app.features.qrcode.QrCodeScannerFragment @@ -206,7 +207,11 @@ class CreateDirectRoomActivity : SimpleFragmentActivity() { } private fun renderCreationSuccess(roomId: String) { - navigator.openRoom(this, roomId) + navigator.openRoom( + context = this, + roomId = roomId, + trigger = ViewRoom.Trigger.MessageUser + ) finish() } diff --git a/vector/src/main/java/im/vector/app/features/createdirect/CreateDirectRoomViewModel.kt b/vector/src/main/java/im/vector/app/features/createdirect/CreateDirectRoomViewModel.kt index 0d67390654..a507f0d099 100644 --- a/vector/src/main/java/im/vector/app/features/createdirect/CreateDirectRoomViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/createdirect/CreateDirectRoomViewModel.kt @@ -36,6 +36,7 @@ import kotlinx.coroutines.launch import org.matrix.android.sdk.api.extensions.orFalse import org.matrix.android.sdk.api.raw.RawService import org.matrix.android.sdk.api.session.Session +import org.matrix.android.sdk.api.session.getUser import org.matrix.android.sdk.api.session.permalinks.PermalinkData import org.matrix.android.sdk.api.session.permalinks.PermalinkParser import org.matrix.android.sdk.api.session.room.model.create.CreateRoomParams @@ -74,7 +75,11 @@ class CreateDirectRoomViewModel @AssistedInject constructor( _viewEvents.post(CreateDirectRoomViewEvents.DmSelf) } else { // Try to get user from known users and fall back to creating a User object from MXID - val qrInvitee = if (session.getUser(mxid) != null) session.getUser(mxid)!! else User(mxid, null, null) + val qrInvitee = if (session.getUser(mxid) != null) { + session.getUser(mxid)!! + } else { + User(mxid, null, null) + } onSubmitInvitees(setOf(PendingSelection.UserPendingSelection(qrInvitee))) } } @@ -85,7 +90,7 @@ class CreateDirectRoomViewModel @AssistedInject constructor( */ private fun onSubmitInvitees(selections: Set) { val existingRoomId = selections.singleOrNull()?.getMxId()?.let { userId -> - session.getExistingDirectRoomWithUser(userId) + session.roomService().getExistingDirectRoomWithUser(userId) } if (existingRoomId != null) { // Do not create a new DM, just tell that the creation is successful by passing the existing roomId @@ -119,7 +124,7 @@ class CreateDirectRoomViewModel @AssistedInject constructor( } val result = runCatchingToAsync { - session.createRoom(roomParams) + session.roomService().createRoom(roomParams) } analyticsTracker.capture(CreatedRoom(isDM = roomParams.isDirect.orFalse())) diff --git a/vector/src/main/java/im/vector/app/features/createdirect/DirectRoomHelper.kt b/vector/src/main/java/im/vector/app/features/createdirect/DirectRoomHelper.kt index e9f0cbfea4..de2027f2a5 100644 --- a/vector/src/main/java/im/vector/app/features/createdirect/DirectRoomHelper.kt +++ b/vector/src/main/java/im/vector/app/features/createdirect/DirectRoomHelper.kt @@ -34,7 +34,7 @@ class DirectRoomHelper @Inject constructor( ) { suspend fun ensureDMExists(userId: String): String { - val existingRoomId = tryOrNull { session.getExistingDirectRoomWithUser(userId) } + val existingRoomId = tryOrNull { session.roomService().getExistingDirectRoomWithUser(userId) } val roomId: String if (existingRoomId != null) { roomId = existingRoomId @@ -48,7 +48,7 @@ class DirectRoomHelper @Inject constructor( setDirectMessage() enableEncryptionIfInvitedUsersSupportIt = adminE2EByDefault } - roomId = session.createRoom(roomParams) + roomId = session.roomService().createRoom(roomParams) analyticsTracker.capture(CreatedRoom(isDM = roomParams.isDirect.orFalse())) } return roomId diff --git a/vector/src/main/java/im/vector/app/features/crypto/keys/KeysExporter.kt b/vector/src/main/java/im/vector/app/features/crypto/keys/KeysExporter.kt index f40f126d2c..f5346980cb 100644 --- a/vector/src/main/java/im/vector/app/features/crypto/keys/KeysExporter.kt +++ b/vector/src/main/java/im/vector/app/features/crypto/keys/KeysExporter.kt @@ -30,7 +30,7 @@ class KeysExporter @Inject constructor( private val dispatchers: CoroutineDispatchers ) { /** - * Export keys and write them to the provided uri + * Export keys and write them to the provided uri. */ suspend fun export(password: String, uri: Uri) { withContext(dispatchers.io) { diff --git a/vector/src/main/java/im/vector/app/features/crypto/keys/KeysImporter.kt b/vector/src/main/java/im/vector/app/features/crypto/keys/KeysImporter.kt index 9b1d29fa25..4b27111851 100644 --- a/vector/src/main/java/im/vector/app/features/crypto/keys/KeysImporter.kt +++ b/vector/src/main/java/im/vector/app/features/crypto/keys/KeysImporter.kt @@ -31,7 +31,7 @@ class KeysImporter @Inject constructor( private val session: Session ) { /** - * Import keys from provided Uri + * Import keys from provided Uri. */ suspend fun import(uri: Uri, mimetype: String?, diff --git a/vector/src/main/java/im/vector/app/features/crypto/keysbackup/restore/KeysBackupRestoreActivity.kt b/vector/src/main/java/im/vector/app/features/crypto/keysbackup/restore/KeysBackupRestoreActivity.kt index a4f6587be4..a32cd7caa7 100644 --- a/vector/src/main/java/im/vector/app/features/crypto/keysbackup/restore/KeysBackupRestoreActivity.kt +++ b/vector/src/main/java/im/vector/app/features/crypto/keysbackup/restore/KeysBackupRestoreActivity.kt @@ -128,7 +128,7 @@ class KeysBackupRestoreActivity : SimpleFragmentActivity() { } private fun launch4SActivity() { - SharedSecureStorageActivity.newIntent( + SharedSecureStorageActivity.newReadIntent( context = this, keyId = null, // default key requestedSecrets = listOf(KEYBACKUP_SECRET_SSSS_NAME), diff --git a/vector/src/main/java/im/vector/app/features/crypto/keysbackup/restore/KeysBackupRestoreFromKeyFragment.kt b/vector/src/main/java/im/vector/app/features/crypto/keysbackup/restore/KeysBackupRestoreFromKeyFragment.kt index 40ad1372fb..42605a850b 100644 --- a/vector/src/main/java/im/vector/app/features/crypto/keysbackup/restore/KeysBackupRestoreFromKeyFragment.kt +++ b/vector/src/main/java/im/vector/app/features/crypto/keysbackup/restore/KeysBackupRestoreFromKeyFragment.kt @@ -31,7 +31,7 @@ import org.matrix.android.sdk.api.extensions.tryOrNull import javax.inject.Inject class KeysBackupRestoreFromKeyFragment @Inject constructor() : - VectorBaseFragment() { + VectorBaseFragment() { override fun getBinding(inflater: LayoutInflater, container: ViewGroup?): FragmentKeysBackupRestoreFromKeyBinding { return FragmentKeysBackupRestoreFromKeyBinding.inflate(inflater, container, false) diff --git a/vector/src/main/java/im/vector/app/features/crypto/keysbackup/restore/KeysBackupRestoreSharedViewModel.kt b/vector/src/main/java/im/vector/app/features/crypto/keysbackup/restore/KeysBackupRestoreSharedViewModel.kt index cf684680ba..a8c3f41efe 100644 --- a/vector/src/main/java/im/vector/app/features/crypto/keysbackup/restore/KeysBackupRestoreSharedViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/crypto/keysbackup/restore/KeysBackupRestoreSharedViewModel.kt @@ -83,29 +83,45 @@ class KeysBackupRestoreSharedViewModel @Inject constructor( val progressObserver = object : StepProgressListener { override fun onStepProgress(step: StepProgressListener.Step) { when (step) { - is StepProgressListener.Step.ComputingKey -> { - loadingEvent.postValue(WaitingViewData(stringProvider.getString(R.string.keys_backup_restoring_waiting_message) + - "\n" + stringProvider.getString(R.string.keys_backup_restoring_computing_key_waiting_message), - step.progress, - step.total)) + is StepProgressListener.Step.ComputingKey -> { + loadingEvent.postValue( + WaitingViewData( + stringProvider.getString(R.string.keys_backup_restoring_waiting_message) + + "\n" + stringProvider.getString(R.string.keys_backup_restoring_computing_key_waiting_message), + step.progress, + step.total + ) + ) } is StepProgressListener.Step.DownloadingKey -> { - loadingEvent.postValue(WaitingViewData(stringProvider.getString(R.string.keys_backup_restoring_waiting_message) + - "\n" + stringProvider.getString(R.string.keys_backup_restoring_downloading_backup_waiting_message), - isIndeterminate = true)) + loadingEvent.postValue( + WaitingViewData( + stringProvider.getString(R.string.keys_backup_restoring_waiting_message) + + "\n" + stringProvider.getString(R.string.keys_backup_restoring_downloading_backup_waiting_message), + isIndeterminate = true + ) + ) } - is StepProgressListener.Step.ImportingKey -> { + is StepProgressListener.Step.ImportingKey -> { Timber.d("backupKeys.ImportingKey.progress: ${step.progress}") // Progress 0 can take a while, display an indeterminate progress in this case if (step.progress == 0) { - loadingEvent.postValue(WaitingViewData(stringProvider.getString(R.string.keys_backup_restoring_waiting_message) + - "\n" + stringProvider.getString(R.string.keys_backup_restoring_importing_keys_waiting_message), - isIndeterminate = true)) + loadingEvent.postValue( + WaitingViewData( + stringProvider.getString(R.string.keys_backup_restoring_waiting_message) + + "\n" + stringProvider.getString(R.string.keys_backup_restoring_importing_keys_waiting_message), + isIndeterminate = true + ) + ) } else { - loadingEvent.postValue(WaitingViewData(stringProvider.getString(R.string.keys_backup_restoring_waiting_message) + - "\n" + stringProvider.getString(R.string.keys_backup_restoring_importing_keys_waiting_message), - step.progress, - step.total)) + loadingEvent.postValue( + WaitingViewData( + stringProvider.getString(R.string.keys_backup_restoring_waiting_message) + + "\n" + stringProvider.getString(R.string.keys_backup_restoring_importing_keys_waiting_message), + step.progress, + step.total + ) + ) } } } @@ -170,7 +186,7 @@ class KeysBackupRestoreSharedViewModel @Inject constructor( fun handleGotSecretFromSSSS(cipherData: String, alias: String) { try { cipherData.fromBase64().inputStream().use { ins -> - val res = session.loadSecureSecret>(ins, alias) + val res = session.secureStorageService().loadSecureSecret>(ins, alias) val secret = res?.get(KEYBACKUP_SECRET_SSSS_NAME) if (secret == null) { _navigateEvent.postValue( @@ -205,7 +221,8 @@ class KeysBackupRestoreSharedViewModel @Inject constructor( try { val result = awaitCallback { - keysBackup.restoreKeyBackupWithPassword(keyVersion, + keysBackup.restoreKeyBackupWithPassword( + keyVersion, passphrase, null, session.myUserId, @@ -230,7 +247,8 @@ class KeysBackupRestoreSharedViewModel @Inject constructor( try { val result = awaitCallback { - keysBackup.restoreKeysWithRecoveryKey(keyVersion, + keysBackup.restoreKeysWithRecoveryKey( + keyVersion, recoveryKey, null, session.myUserId, @@ -252,7 +270,7 @@ class KeysBackupRestoreSharedViewModel @Inject constructor( ?: return false // Some sanity ? - val defaultKeyResult = session.sharedSecretStorageService.getDefaultKey() + val defaultKeyResult = session.sharedSecretStorageService().getDefaultKey() val keyInfo = (defaultKeyResult as? KeyInfoResult.Success)?.keyInfo ?: return false diff --git a/vector/src/main/java/im/vector/app/features/crypto/keysbackup/restore/KeysBackupRestoreSuccessFragment.kt b/vector/src/main/java/im/vector/app/features/crypto/keysbackup/restore/KeysBackupRestoreSuccessFragment.kt index c4663fd3bc..d26c1e2134 100644 --- a/vector/src/main/java/im/vector/app/features/crypto/keysbackup/restore/KeysBackupRestoreSuccessFragment.kt +++ b/vector/src/main/java/im/vector/app/features/crypto/keysbackup/restore/KeysBackupRestoreSuccessFragment.kt @@ -40,10 +40,14 @@ class KeysBackupRestoreSuccessFragment @Inject constructor() : VectorBaseFragmen if (compareValues(sharedViewModel.importKeyResult?.totalNumberOfKeys, 0) > 0) { sharedViewModel.importKeyResult?.let { - val part1 = resources.getQuantityString(R.plurals.keys_backup_restore_success_description_part1, - it.totalNumberOfKeys, it.totalNumberOfKeys) - val part2 = resources.getQuantityString(R.plurals.keys_backup_restore_success_description_part2, - it.successfullyNumberOfImportedKeys, it.successfullyNumberOfImportedKeys) + val part1 = resources.getQuantityString( + R.plurals.keys_backup_restore_success_description_part1, + it.totalNumberOfKeys, it.totalNumberOfKeys + ) + val part2 = resources.getQuantityString( + R.plurals.keys_backup_restore_success_description_part2, + it.successfullyNumberOfImportedKeys, it.successfullyNumberOfImportedKeys + ) views.successDetailsText.text = String.format("%s\n%s", part1, part2) } // We don't put emoji in string xml as it will crash on old devices diff --git a/vector/src/main/java/im/vector/app/features/crypto/keysbackup/settings/KeyBackupSettingsAction.kt b/vector/src/main/java/im/vector/app/features/crypto/keysbackup/settings/KeyBackupSettingsAction.kt index 776c7bb521..0d19ae630b 100644 --- a/vector/src/main/java/im/vector/app/features/crypto/keysbackup/settings/KeyBackupSettingsAction.kt +++ b/vector/src/main/java/im/vector/app/features/crypto/keysbackup/settings/KeyBackupSettingsAction.kt @@ -22,4 +22,8 @@ sealed class KeyBackupSettingsAction : VectorViewModelAction { object Init : KeyBackupSettingsAction() object GetKeyBackupTrust : KeyBackupSettingsAction() object DeleteKeyBackup : KeyBackupSettingsAction() + object SetUpKeyBackup : KeyBackupSettingsAction() + data class StoreIn4SSuccess(val recoveryKey: String, val alias: String) : KeyBackupSettingsAction() + object StoreIn4SReset : KeyBackupSettingsAction() + object StoreIn4SFailure : KeyBackupSettingsAction() } diff --git a/vector/src/main/java/im/vector/app/features/crypto/keysbackup/settings/KeysBackupManageActivity.kt b/vector/src/main/java/im/vector/app/features/crypto/keysbackup/settings/KeysBackupManageActivity.kt index 13df109dd5..e58746193b 100644 --- a/vector/src/main/java/im/vector/app/features/crypto/keysbackup/settings/KeysBackupManageActivity.kt +++ b/vector/src/main/java/im/vector/app/features/crypto/keysbackup/settings/KeysBackupManageActivity.kt @@ -15,6 +15,7 @@ */ package im.vector.app.features.crypto.keysbackup.settings +import android.app.Activity import android.content.Context import android.content.Intent import com.airbnb.mvrx.Fail @@ -23,9 +24,13 @@ import com.airbnb.mvrx.viewModel import com.google.android.material.dialog.MaterialAlertDialogBuilder import dagger.hilt.android.AndroidEntryPoint import im.vector.app.R +import im.vector.app.core.extensions.registerStartForActivityResult import im.vector.app.core.extensions.replaceFragment import im.vector.app.core.platform.SimpleFragmentActivity import im.vector.app.core.platform.WaitingViewData +import im.vector.app.features.crypto.keysbackup.setup.KeysBackupSetupActivity +import im.vector.app.features.crypto.quads.SharedSecureStorageActivity +import org.matrix.android.sdk.api.session.crypto.crosssigning.KEYBACKUP_SECRET_SSSS_NAME @AndroidEntryPoint class KeysBackupManageActivity : SimpleFragmentActivity() { @@ -41,6 +46,21 @@ class KeysBackupManageActivity : SimpleFragmentActivity() { private val viewModel: KeysBackupSettingsViewModel by viewModel() + private val secretStartForActivityResult = registerStartForActivityResult { activityResult -> + if (activityResult.resultCode == Activity.RESULT_OK) { + val result = activityResult.data?.getStringExtra(SharedSecureStorageActivity.EXTRA_DATA_RESULT) + val reset = activityResult.data?.getBooleanExtra(SharedSecureStorageActivity.EXTRA_DATA_RESET, false) ?: false + if (result != null) { + viewModel.handle(KeyBackupSettingsAction.StoreIn4SSuccess(result, SharedSecureStorageActivity.DEFAULT_RESULT_KEYSTORE_ALIAS)) + } else if (reset) { + // all have been reset so a new backup would have been created + viewModel.handle(KeyBackupSettingsAction.StoreIn4SReset) + } + } else { + viewModel.handle(KeyBackupSettingsAction.StoreIn4SFailure) + } + } + override fun initUiAndData() { super.initUiAndData() if (supportFragmentManager.fragments.isEmpty()) { @@ -69,6 +89,22 @@ class KeysBackupManageActivity : SimpleFragmentActivity() { } } } + + viewModel.observeViewEvents { + when (it) { + KeysBackupViewEvents.OpenLegacyCreateBackup -> { + startActivity(KeysBackupSetupActivity.intent(this, false)) + } + is KeysBackupViewEvents.RequestStore4SSecret -> { + secretStartForActivityResult.launch( + SharedSecureStorageActivity.newWriteIntent( + context = this, + writeSecrets = listOf(KEYBACKUP_SECRET_SSSS_NAME to it.recoveryKey) + ) + ) + } + } + } } override fun onBackPressed() { diff --git a/vector/src/main/java/im/vector/app/features/crypto/keysbackup/settings/KeysBackupSettingsFragment.kt b/vector/src/main/java/im/vector/app/features/crypto/keysbackup/settings/KeysBackupSettingsFragment.kt index 50d5e56483..edc44fa796 100644 --- a/vector/src/main/java/im/vector/app/features/crypto/keysbackup/settings/KeysBackupSettingsFragment.kt +++ b/vector/src/main/java/im/vector/app/features/crypto/keysbackup/settings/KeysBackupSettingsFragment.kt @@ -28,11 +28,10 @@ import im.vector.app.core.extensions.configureWith import im.vector.app.core.platform.VectorBaseFragment import im.vector.app.databinding.FragmentKeysBackupSettingsBinding import im.vector.app.features.crypto.keysbackup.restore.KeysBackupRestoreActivity -import im.vector.app.features.crypto.keysbackup.setup.KeysBackupSetupActivity import javax.inject.Inject class KeysBackupSettingsFragment @Inject constructor(private val keysBackupSettingsRecyclerViewController: KeysBackupSettingsRecyclerViewController) : - VectorBaseFragment(), + VectorBaseFragment(), KeysBackupSettingsRecyclerViewController.Listener { override fun getBinding(inflater: LayoutInflater, container: ViewGroup?): FragmentKeysBackupSettingsBinding { @@ -58,9 +57,7 @@ class KeysBackupSettingsFragment @Inject constructor(private val keysBackupSetti } override fun didSelectSetupMessageRecovery() { - context?.let { - startActivity(KeysBackupSetupActivity.intent(it, false)) - } + viewModel.handle(KeyBackupSettingsAction.SetUpKeyBackup) } override fun didSelectRestoreMessageRecovery() { diff --git a/vector/src/main/java/im/vector/app/features/crypto/keysbackup/settings/KeysBackupSettingsRecyclerViewController.kt b/vector/src/main/java/im/vector/app/features/crypto/keysbackup/settings/KeysBackupSettingsRecyclerViewController.kt index 9bf8050939..d13b97a9ba 100644 --- a/vector/src/main/java/im/vector/app/features/crypto/keysbackup/settings/KeysBackupSettingsRecyclerViewController.kt +++ b/vector/src/main/java/im/vector/app/features/crypto/keysbackup/settings/KeysBackupSettingsRecyclerViewController.kt @@ -29,9 +29,11 @@ import im.vector.app.core.ui.list.ItemStyle import im.vector.app.core.ui.list.genericItem import im.vector.app.features.settings.VectorPreferences import im.vector.lib.core.utils.epoxy.charsequence.toEpoxyCharSequence +import org.matrix.android.sdk.api.extensions.orFalse import org.matrix.android.sdk.api.session.Session import org.matrix.android.sdk.api.session.crypto.keysbackup.KeysBackupState import org.matrix.android.sdk.api.session.crypto.keysbackup.KeysBackupVersionTrust +import org.matrix.android.sdk.api.session.crypto.keysbackup.KeysBackupVersionTrustSignature import java.util.UUID import javax.inject.Inject @@ -132,9 +134,11 @@ class KeysBackupSettingsRecyclerViewController @Inject constructor( if (data.keysBackupVersionTrust()?.usable == false) { description(host.stringProvider.getString(R.string.keys_backup_settings_untrusted_backup).toEpoxyCharSequence()) } else { - description(host.stringProvider - .getQuantityString(R.plurals.keys_backup_info_keys_backing_up, remainingKeysToBackup, remainingKeysToBackup) - .toEpoxyCharSequence()) + description( + host.stringProvider + .getQuantityString(R.plurals.keys_backup_info_keys_backing_up, remainingKeysToBackup, remainingKeysToBackup) + .toEpoxyCharSequence() + ) } } @@ -189,57 +193,105 @@ class KeysBackupSettingsRecyclerViewController @Inject constructor( } } is Success -> { - keysVersionTrust().signatures.forEach { - genericItem { - id(UUID.randomUUID().toString()) - title(host.stringProvider.getString(R.string.keys_backup_info_title_signature).toEpoxyCharSequence()) - - val isDeviceKnown = it.device != null - val isDeviceVerified = it.device?.isVerified ?: false - val isSignatureValid = it.valid - val deviceId: String = it.deviceId ?: "" - - if (!isDeviceKnown) { - description(host.stringProvider - .getString(R.string.keys_backup_settings_signature_from_unknown_device, deviceId) - .toEpoxyCharSequence()) - endIconResourceId(R.drawable.e2e_warning) - } else { - if (isSignatureValid) { - if (host.session.sessionParams.deviceId == it.deviceId) { - description(host.stringProvider - .getString(R.string.keys_backup_settings_valid_signature_from_this_device) - .toEpoxyCharSequence()) + keysVersionTrust() + .signatures + .filterIsInstance() + .forEach { + val isUserVerified = it.cryptoCrossSigningKey?.trustLevel?.isVerified().orFalse() + val isSignatureValid = it.valid + val userId: String = it.cryptoCrossSigningKey?.userId ?: "" + if (userId == session.sessionParams.userId && isSignatureValid && isUserVerified) { + genericItem { + id(UUID.randomUUID().toString()) + title(host.stringProvider.getString(R.string.keys_backup_info_title_signature).toEpoxyCharSequence()) + description( + host.stringProvider + .getString(R.string.keys_backup_settings_signature_from_this_user) + .toEpoxyCharSequence() + ) endIconResourceId(R.drawable.e2e_verified) - } else { - if (isDeviceVerified) { - description(host.stringProvider - .getString(R.string.keys_backup_settings_valid_signature_from_verified_device, deviceId) - .toEpoxyCharSequence()) - endIconResourceId(R.drawable.e2e_verified) - } else { - description(host.stringProvider - .getString(R.string.keys_backup_settings_valid_signature_from_unverified_device, deviceId) - .toEpoxyCharSequence()) - endIconResourceId(R.drawable.e2e_warning) - } - } - } else { - // Invalid signature - endIconResourceId(R.drawable.e2e_warning) - if (isDeviceVerified) { - description(host.stringProvider - .getString(R.string.keys_backup_settings_invalid_signature_from_verified_device, deviceId) - .toEpoxyCharSequence()) - } else { - description(host.stringProvider - .getString(R.string.keys_backup_settings_invalid_signature_from_unverified_device, deviceId) - .toEpoxyCharSequence()) } } } - } - } // end for each + + keysVersionTrust() + .signatures + .filterIsInstance() + .forEach { + genericItem { + id(UUID.randomUUID().toString()) + title(host.stringProvider.getString(R.string.keys_backup_info_title_signature).toEpoxyCharSequence()) + + val isDeviceKnown = it.device != null + val isDeviceVerified = it.device?.isVerified ?: false + val isSignatureValid = it.valid + val deviceId: String = it.deviceId ?: "" + + if (!isDeviceKnown) { + description( + host.stringProvider + .getString(R.string.keys_backup_settings_signature_from_unknown_device, deviceId) + .toEpoxyCharSequence() + ) + endIconResourceId(R.drawable.e2e_warning) + } else { + if (isSignatureValid) { + if (host.session.sessionParams.deviceId == it.deviceId) { + description( + host.stringProvider + .getString(R.string.keys_backup_settings_valid_signature_from_this_device) + .toEpoxyCharSequence() + ) + endIconResourceId(R.drawable.e2e_verified) + } else { + if (isDeviceVerified) { + description( + host.stringProvider + .getString( + R.string.keys_backup_settings_valid_signature_from_verified_device, + deviceId + ) + .toEpoxyCharSequence() + ) + endIconResourceId(R.drawable.e2e_verified) + } else { + description( + host.stringProvider + .getString( + R.string.keys_backup_settings_valid_signature_from_unverified_device, + deviceId + ) + .toEpoxyCharSequence() + ) + endIconResourceId(R.drawable.e2e_warning) + } + } + } else { + // Invalid signature + endIconResourceId(R.drawable.e2e_warning) + if (isDeviceVerified) { + description( + host.stringProvider + .getString( + R.string.keys_backup_settings_invalid_signature_from_verified_device, + deviceId + ) + .toEpoxyCharSequence() + ) + } else { + description( + host.stringProvider + .getString( + R.string.keys_backup_settings_invalid_signature_from_unverified_device, + deviceId + ) + .toEpoxyCharSequence() + ) + } + } + } + } + } // end for each } is Fail -> { errorWithRetryItem { diff --git a/vector/src/main/java/im/vector/app/features/crypto/keysbackup/settings/KeysBackupSettingsViewModel.kt b/vector/src/main/java/im/vector/app/features/crypto/keysbackup/settings/KeysBackupSettingsViewModel.kt index ca6edf0941..b47b84af71 100644 --- a/vector/src/main/java/im/vector/app/features/crypto/keysbackup/settings/KeysBackupSettingsViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/crypto/keysbackup/settings/KeysBackupSettingsViewModel.kt @@ -25,8 +25,8 @@ 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 kotlinx.coroutines.launch import org.matrix.android.sdk.api.MatrixCallback import org.matrix.android.sdk.api.NoOpMatrixCallback import org.matrix.android.sdk.api.session.Session @@ -34,10 +34,16 @@ import org.matrix.android.sdk.api.session.crypto.keysbackup.KeysBackupService import org.matrix.android.sdk.api.session.crypto.keysbackup.KeysBackupState import org.matrix.android.sdk.api.session.crypto.keysbackup.KeysBackupStateListener import org.matrix.android.sdk.api.session.crypto.keysbackup.KeysBackupVersionTrust +import org.matrix.android.sdk.api.session.crypto.keysbackup.KeysVersion +import org.matrix.android.sdk.api.session.crypto.keysbackup.MegolmBackupCreationInfo +import org.matrix.android.sdk.api.session.crypto.keysbackup.extractCurveKeyFromRecoveryKey +import org.matrix.android.sdk.api.util.awaitCallback +import org.matrix.android.sdk.api.util.toBase64NoPadding +import timber.log.Timber class KeysBackupSettingsViewModel @AssistedInject constructor(@Assisted initialState: KeysBackupSettingViewState, - session: Session -) : VectorViewModel(initialState), + private val session: Session +) : VectorViewModel(initialState), KeysBackupStateListener { @AssistedFactory @@ -49,6 +55,8 @@ class KeysBackupSettingsViewModel @AssistedInject constructor(@Assisted initialS private val keysBackupService: KeysBackupService = session.cryptoService().keysBackupService() + var pendingBackupCreationInfo: MegolmBackupCreationInfo? = null + init { setState { this.copy( @@ -62,9 +70,18 @@ class KeysBackupSettingsViewModel @AssistedInject constructor(@Assisted initialS override fun handle(action: KeyBackupSettingsAction) { when (action) { - KeyBackupSettingsAction.Init -> init() - KeyBackupSettingsAction.GetKeyBackupTrust -> getKeysBackupTrust() - KeyBackupSettingsAction.DeleteKeyBackup -> deleteCurrentBackup() + KeyBackupSettingsAction.Init -> init() + KeyBackupSettingsAction.GetKeyBackupTrust -> getKeysBackupTrust() + KeyBackupSettingsAction.DeleteKeyBackup -> deleteCurrentBackup() + KeyBackupSettingsAction.SetUpKeyBackup -> viewModelScope.launch { + setUpKeyBackup() + } + KeyBackupSettingsAction.StoreIn4SReset, + KeyBackupSettingsAction.StoreIn4SFailure -> { + pendingBackupCreationInfo = null + // nothing to do just stay on fragment + } + is KeyBackupSettingsAction.StoreIn4SSuccess -> viewModelScope.launch { completeBackupCreation() } } } @@ -120,6 +137,41 @@ class KeysBackupSettingsViewModel @AssistedInject constructor(@Assisted initialS getKeysBackupTrust() } + suspend fun setUpKeyBackup() { + // We need to check if 4S is enabled first. + // If it is we need to use it, generate a random key + // for the backup and store it in the 4S + if (session.sharedSecretStorageService().isRecoverySetup()) { + val creationInfo = awaitCallback { + session.cryptoService().keysBackupService().prepareKeysBackupVersion(null, null, it) + } + pendingBackupCreationInfo = creationInfo + val recoveryKey = extractCurveKeyFromRecoveryKey(creationInfo.recoveryKey)?.toBase64NoPadding() + _viewEvents.post(KeysBackupViewEvents.RequestStore4SSecret(recoveryKey!!)) + } else { + // No 4S so we can open legacy flow + _viewEvents.post(KeysBackupViewEvents.OpenLegacyCreateBackup) + } + } + + suspend fun completeBackupCreation() { + val info = pendingBackupCreationInfo ?: return + try { + val version = awaitCallback { + session.cryptoService().keysBackupService().createKeysBackupVersion(info, it) + } + // Save it for gossiping + Timber.d("## BootstrapCrossSigningTask: Creating 4S - Save megolm backup key for gossiping") + session.cryptoService().keysBackupService().saveBackupRecoveryKey(info.recoveryKey, version = version.version) + } catch (failure: Throwable) { + // XXX mm... failed we should remove what we put in 4S, as it was not created? + + // for now just stay on the screen, user can retry, there is no api to delete account data + } finally { + pendingBackupCreationInfo = null + } + } + private fun deleteCurrentBackup() { val keysBackupService = keysBackupService diff --git a/vector/src/main/java/im/vector/app/features/crypto/keysbackup/settings/KeysBackupViewEvents.kt b/vector/src/main/java/im/vector/app/features/crypto/keysbackup/settings/KeysBackupViewEvents.kt new file mode 100644 index 0000000000..b39a516772 --- /dev/null +++ b/vector/src/main/java/im/vector/app/features/crypto/keysbackup/settings/KeysBackupViewEvents.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.crypto.keysbackup.settings + +import im.vector.app.core.platform.VectorViewEvents + +sealed class KeysBackupViewEvents : VectorViewEvents { + object OpenLegacyCreateBackup : KeysBackupViewEvents() + data class RequestStore4SSecret(val recoveryKey: String) : KeysBackupViewEvents() +} diff --git a/vector/src/main/java/im/vector/app/features/crypto/keysbackup/setup/KeysBackupSetupSharedViewModel.kt b/vector/src/main/java/im/vector/app/features/crypto/keysbackup/setup/KeysBackupSetupSharedViewModel.kt index 34aa7ba0ee..dfa7d1aaa3 100644 --- a/vector/src/main/java/im/vector/app/features/crypto/keysbackup/setup/KeysBackupSetupSharedViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/crypto/keysbackup/setup/KeysBackupSetupSharedViewModel.kt @@ -22,6 +22,7 @@ import androidx.lifecycle.ViewModel import com.nulabinc.zxcvbn.Strength import im.vector.app.R import im.vector.app.core.platform.WaitingViewData +import im.vector.app.core.time.Clock import im.vector.app.core.utils.LiveEvent import org.matrix.android.sdk.api.MatrixCallback import org.matrix.android.sdk.api.listeners.ProgressListener @@ -37,7 +38,9 @@ import javax.inject.Inject /** * The shared view model between all fragments. */ -class KeysBackupSetupSharedViewModel @Inject constructor() : ViewModel() { +class KeysBackupSetupSharedViewModel @Inject constructor( + private val clock: Clock, +) : ViewModel() { companion object { const val NAVIGATE_TO_STEP_2 = "NAVIGATE_TO_STEP_2" @@ -85,7 +88,7 @@ class KeysBackupSetupSharedViewModel @Inject constructor() : ViewModel() { fun prepareRecoveryKey(context: Context, withPassphrase: String?) { // Update requestId - currentRequestId.value = System.currentTimeMillis() + currentRequestId.value = clock.epochMillis() isCreatingBackupVersion.value = true recoveryKey.value = null @@ -101,9 +104,11 @@ class KeysBackupSetupSharedViewModel @Inject constructor() : ViewModel() { return } - loadingStatus.value = WaitingViewData(context.getString(R.string.keys_backup_setup_step3_generating_key_status), + loadingStatus.value = WaitingViewData( + context.getString(R.string.keys_backup_setup_step3_generating_key_status), progress, - total) + total + ) } }, object : MatrixCallback { diff --git a/vector/src/main/java/im/vector/app/features/crypto/keysbackup/setup/KeysBackupSetupStep3Fragment.kt b/vector/src/main/java/im/vector/app/features/crypto/keysbackup/setup/KeysBackupSetupStep3Fragment.kt index e5d7ade3ce..61148f3aab 100644 --- a/vector/src/main/java/im/vector/app/features/crypto/keysbackup/setup/KeysBackupSetupStep3Fragment.kt +++ b/vector/src/main/java/im/vector/app/features/crypto/keysbackup/setup/KeysBackupSetupStep3Fragment.kt @@ -146,7 +146,8 @@ class KeysBackupSetupStep3Fragment @Inject constructor() : VectorBaseFragment, - val resultKeyStoreAlias: String + val requestedSecrets: List = emptyList(), + val resultKeyStoreAlias: String, + val writeSecrets: List> = emptyList(), ) : Parcelable private val viewModel: SharedSecureStorageViewModel by viewModel() @@ -148,17 +149,37 @@ class SharedSecureStorageActivity : const val EXTRA_DATA_RESET = "EXTRA_DATA_RESET" const val DEFAULT_RESULT_KEYSTORE_ALIAS = "SharedSecureStorageActivity" - fun newIntent(context: Context, - keyId: String? = null, - requestedSecrets: List, - resultKeyStoreAlias: String = DEFAULT_RESULT_KEYSTORE_ALIAS): Intent { + fun newReadIntent(context: Context, + keyId: String? = null, + requestedSecrets: List, + resultKeyStoreAlias: String = DEFAULT_RESULT_KEYSTORE_ALIAS): Intent { require(requestedSecrets.isNotEmpty()) return Intent(context, SharedSecureStorageActivity::class.java).also { - it.putExtra(Mavericks.KEY_ARG, Args( - keyId, - requestedSecrets, - resultKeyStoreAlias - )) + it.putExtra( + Mavericks.KEY_ARG, + Args( + keyId = keyId, + requestedSecrets = requestedSecrets, + resultKeyStoreAlias = resultKeyStoreAlias + ) + ) + } + } + + fun newWriteIntent(context: Context, + keyId: String? = null, + writeSecrets: List>, + resultKeyStoreAlias: String = DEFAULT_RESULT_KEYSTORE_ALIAS): Intent { + require(writeSecrets.isNotEmpty()) + return Intent(context, SharedSecureStorageActivity::class.java).also { + it.putExtra( + Mavericks.KEY_ARG, + Args( + keyId = keyId, + writeSecrets = writeSecrets, + resultKeyStoreAlias = resultKeyStoreAlias + ) + ) } } } diff --git a/vector/src/main/java/im/vector/app/features/crypto/quads/SharedSecureStorageViewModel.kt b/vector/src/main/java/im/vector/app/features/crypto/quads/SharedSecureStorageViewModel.kt index 51d8b3a8d5..e045ac020d 100644 --- a/vector/src/main/java/im/vector/app/features/crypto/quads/SharedSecureStorageViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/crypto/quads/SharedSecureStorageViewModel.kt @@ -39,13 +39,20 @@ import kotlinx.coroutines.withContext import org.matrix.android.sdk.api.listeners.ProgressListener import org.matrix.android.sdk.api.session.Session import org.matrix.android.sdk.api.session.securestorage.IntegrityResult +import org.matrix.android.sdk.api.session.securestorage.KeyInfo import org.matrix.android.sdk.api.session.securestorage.KeyInfoResult import org.matrix.android.sdk.api.session.securestorage.RawBytesKeySpec +import org.matrix.android.sdk.api.session.securestorage.SharedSecretStorageService import org.matrix.android.sdk.api.util.toBase64NoPadding import org.matrix.android.sdk.flow.flow import timber.log.Timber import java.io.ByteArrayOutputStream +sealed class RequestType { + data class ReadSecrets(val secretsName: List) : RequestType() + data class WriteSecrets(val secretsNameValue: List>) : RequestType() +} + data class SharedSecureStorageViewState( val ready: Boolean = false, val hasPassphrase: Boolean = true, @@ -55,13 +62,17 @@ data class SharedSecureStorageViewState( val showResetAllAction: Boolean = false, val userId: String = "", val keyId: String?, - val requestedSecrets: List, + val requestType: RequestType, val resultKeyStoreAlias: String ) : MavericksState { constructor(args: SharedSecureStorageActivity.Args) : this( keyId = args.keyId, - requestedSecrets = args.requestedSecrets, + requestType = if (args.writeSecrets.isNotEmpty()) { + RequestType.WriteSecrets(args.writeSecrets) + } else { + RequestType.ReadSecrets(args.requestedSecrets) + }, resultKeyStoreAlias = args.resultKeyStoreAlias ) @@ -76,7 +87,7 @@ class SharedSecureStorageViewModel @AssistedInject constructor( @Assisted private val initialState: SharedSecureStorageViewState, private val stringProvider: StringProvider, private val session: Session) : - VectorViewModel(initialState) { + VectorViewModel(initialState) { @AssistedFactory interface Factory : MavericksAssistedViewModelFactory { @@ -87,17 +98,20 @@ class SharedSecureStorageViewModel @AssistedInject constructor( setState { copy(userId = session.myUserId) } - val integrityResult = session.sharedSecretStorageService.checkShouldBeAbleToAccessSecrets(initialState.requestedSecrets, initialState.keyId) - if (integrityResult !is IntegrityResult.Success) { - _viewEvents.post( - SharedSecureStorageViewEvent.Error( - stringProvider.getString(R.string.enter_secret_storage_invalid), - true - ) - ) + if (initialState.requestType is RequestType.ReadSecrets) { + val integrityResult = + session.sharedSecretStorageService().checkShouldBeAbleToAccessSecrets(initialState.requestType.secretsName, initialState.keyId) + if (integrityResult !is IntegrityResult.Success) { + _viewEvents.post( + SharedSecureStorageViewEvent.Error( + stringProvider.getString(R.string.enter_secret_storage_invalid), + true + ) + ) + } } - val keyResult = initialState.keyId?.let { session.sharedSecretStorageService.getKey(it) } - ?: session.sharedSecretStorageService.getDefaultKey() + val keyResult = initialState.keyId?.let { session.sharedSecretStorageService().getKey(it) } + ?: session.sharedSecretStorageService().getDefaultKey() if (!keyResult.isSuccess()) { _viewEvents.post(SharedSecureStorageViewEvent.Dismiss) @@ -134,13 +148,13 @@ class SharedSecureStorageViewModel @AssistedInject constructor( override fun handle(action: SharedSecureStorageAction) = withState { when (action) { - is SharedSecureStorageAction.Cancel -> handleCancel() - is SharedSecureStorageAction.SubmitPassphrase -> handleSubmitPassphrase(action) - SharedSecureStorageAction.UseKey -> handleUseKey() - is SharedSecureStorageAction.SubmitKey -> handleSubmitKey(action) - SharedSecureStorageAction.Back -> handleBack() - SharedSecureStorageAction.ForgotResetAll -> handleResetAll() - SharedSecureStorageAction.DoResetAll -> handleDoResetAll() + is SharedSecureStorageAction.Cancel -> handleCancel() + is SharedSecureStorageAction.SubmitPassphrase -> handleSubmitPassphrase(action) + SharedSecureStorageAction.UseKey -> handleUseKey() + is SharedSecureStorageAction.SubmitKey -> handleSubmitKey(action) + SharedSecureStorageAction.Back -> handleBack() + SharedSecureStorageAction.ForgotResetAll -> handleResetAll() + SharedSecureStorageAction.DoResetAll -> handleDoResetAll() } } @@ -205,7 +219,7 @@ class SharedSecureStorageViewModel @AssistedInject constructor( viewModelScope.launch(Dispatchers.IO) { runCatching { val recoveryKey = action.recoveryKey - val keyInfoResult = session.sharedSecretStorageService.getDefaultKey() + val keyInfoResult = session.sharedSecretStorageService().getDefaultKey() if (!keyInfoResult.isSuccess()) { _viewEvents.post(SharedSecureStorageViewEvent.HideModalLoading) _viewEvents.post(SharedSecureStorageViewEvent.Error(stringProvider.getString(R.string.failed_to_access_secure_storage))) @@ -213,37 +227,28 @@ class SharedSecureStorageViewModel @AssistedInject constructor( } val keyInfo = (keyInfoResult as KeyInfoResult.Success).keyInfo - _viewEvents.post(SharedSecureStorageViewEvent.UpdateLoadingState( - WaitingViewData( - message = stringProvider.getString(R.string.keys_backup_restoring_computing_key_waiting_message), - isIndeterminate = true + _viewEvents.post( + SharedSecureStorageViewEvent.UpdateLoadingState( + WaitingViewData( + message = stringProvider.getString(R.string.keys_backup_restoring_computing_key_waiting_message), + isIndeterminate = true + ) ) - )) + ) val keySpec = RawBytesKeySpec.fromRecoveryKey(recoveryKey) ?: return@launch Unit.also { _viewEvents.post(SharedSecureStorageViewEvent.KeyInlineError(stringProvider.getString(R.string.bootstrap_invalid_recovery_key))) _viewEvents.post(SharedSecureStorageViewEvent.HideModalLoading) setState { copy(checkingSSSSAction = Fail(IllegalArgumentException(stringProvider.getString(R.string.bootstrap_invalid_recovery_key)))) } } - withContext(Dispatchers.IO) { - initialState.requestedSecrets.forEach { - if (session.accountDataService().getUserAccountDataEvent(it) != null) { - val res = session.sharedSecretStorageService.getSecret( - name = it, - keyId = keyInfo.id, - secretKey = keySpec) - decryptedSecretMap[it] = res - } else { - Timber.w("## Cannot find secret $it in SSSS, skip") - } - } + performRequest(keyInfo, keySpec, decryptedSecretMap) } }.fold({ setState { copy(checkingSSSSAction = Success(Unit)) } _viewEvents.post(SharedSecureStorageViewEvent.HideModalLoading) val safeForIntentCypher = ByteArrayOutputStream().also { it.use { - session.securelyStoreObject(decryptedSecretMap as Map, initialState.resultKeyStoreAlias, it) + session.secureStorageService().securelyStoreObject(decryptedSecretMap as Map, initialState.resultKeyStoreAlias, it) } }.toByteArray().toBase64NoPadding() _viewEvents.post(SharedSecureStorageViewEvent.FinishSuccess(safeForIntentCypher)) @@ -255,6 +260,37 @@ class SharedSecureStorageViewModel @AssistedInject constructor( } } + private suspend fun performRequest(keyInfo: KeyInfo, keySpec: RawBytesKeySpec, decryptedSecretMap: HashMap) { + when (val requestType = initialState.requestType) { + is RequestType.ReadSecrets -> { + requestType.secretsName.forEach { + if (session.accountDataService().getUserAccountDataEvent(it) != null) { + val res = session.sharedSecretStorageService().getSecret( + name = it, + keyId = keyInfo.id, + secretKey = keySpec + ) + decryptedSecretMap[it] = res + } else { + Timber.w("## Cannot find secret $it in SSSS, skip") + } + } + } + is RequestType.WriteSecrets -> { + requestType.secretsNameValue.forEach { + val (name, value) = it + + session.sharedSecretStorageService().storeSecret( + name = name, + secretBase64 = value, + keys = listOf(SharedSecretStorageService.KeyRef(keyInfo.id, keySpec)) + ) + decryptedSecretMap[name] = value + } + } + } + } + private fun handleSubmitPassphrase(action: SharedSecureStorageAction.SubmitPassphrase) { _viewEvents.post(SharedSecureStorageViewEvent.ShowModalLoading) val decryptedSecretMap = HashMap() @@ -262,7 +298,7 @@ class SharedSecureStorageViewModel @AssistedInject constructor( viewModelScope.launch(Dispatchers.IO) { runCatching { val passphrase = action.passphrase - val keyInfoResult = session.sharedSecretStorageService.getDefaultKey() + val keyInfoResult = session.sharedSecretStorageService().getDefaultKey() if (!keyInfoResult.isSuccess()) { _viewEvents.post(SharedSecureStorageViewEvent.HideModalLoading) _viewEvents.post(SharedSecureStorageViewEvent.Error("Cannot find ssss key")) @@ -270,41 +306,37 @@ class SharedSecureStorageViewModel @AssistedInject constructor( } val keyInfo = (keyInfoResult as KeyInfoResult.Success).keyInfo - _viewEvents.post(SharedSecureStorageViewEvent.UpdateLoadingState( - WaitingViewData( - message = stringProvider.getString(R.string.keys_backup_restoring_computing_key_waiting_message), - isIndeterminate = true + _viewEvents.post( + SharedSecureStorageViewEvent.UpdateLoadingState( + WaitingViewData( + message = stringProvider.getString(R.string.keys_backup_restoring_computing_key_waiting_message), + isIndeterminate = true + ) ) - )) + ) val keySpec = RawBytesKeySpec.fromPassphrase( passphrase, keyInfo.content.passphrase?.salt ?: "", keyInfo.content.passphrase?.iterations ?: 0, object : ProgressListener { override fun onProgress(progress: Int, total: Int) { - _viewEvents.post(SharedSecureStorageViewEvent.UpdateLoadingState( - WaitingViewData( - message = stringProvider.getString(R.string.keys_backup_restoring_computing_key_waiting_message), - isIndeterminate = false, - progress = progress, - progressTotal = total + _viewEvents.post( + SharedSecureStorageViewEvent.UpdateLoadingState( + WaitingViewData( + message = stringProvider.getString(R.string.keys_backup_restoring_computing_key_waiting_message), + isIndeterminate = false, + progress = progress, + progressTotal = total + ) ) - )) + ) } } ) withContext(Dispatchers.IO) { - initialState.requestedSecrets.forEach { - if (session.accountDataService().getUserAccountDataEvent(it) != null) { - val res = session.sharedSecretStorageService.getSecret( - name = it, - keyId = keyInfo.id, - secretKey = keySpec) - decryptedSecretMap[it] = res - } else { - Timber.w("## Cannot find secret $it in SSSS, skip") - } + withContext(Dispatchers.IO) { + performRequest(keyInfo, keySpec, decryptedSecretMap) } } }.fold({ @@ -312,7 +344,7 @@ class SharedSecureStorageViewModel @AssistedInject constructor( _viewEvents.post(SharedSecureStorageViewEvent.HideModalLoading) val safeForIntentCypher = ByteArrayOutputStream().also { it.use { - session.securelyStoreObject(decryptedSecretMap as Map, initialState.resultKeyStoreAlias, it) + session.secureStorageService().securelyStoreObject(decryptedSecretMap as Map, initialState.resultKeyStoreAlias, it) } }.toByteArray().toBase64NoPadding() _viewEvents.post(SharedSecureStorageViewEvent.FinishSuccess(safeForIntentCypher)) diff --git a/vector/src/main/java/im/vector/app/features/crypto/quads/SharedSecuredStorageResetAllFragment.kt b/vector/src/main/java/im/vector/app/features/crypto/quads/SharedSecuredStorageResetAllFragment.kt index 200b2b73c2..c0d0aa8e76 100644 --- a/vector/src/main/java/im/vector/app/features/crypto/quads/SharedSecuredStorageResetAllFragment.kt +++ b/vector/src/main/java/im/vector/app/features/crypto/quads/SharedSecuredStorageResetAllFragment.kt @@ -30,7 +30,7 @@ import im.vector.app.features.roommemberprofile.devices.DeviceListBottomSheet import javax.inject.Inject class SharedSecuredStorageResetAllFragment @Inject constructor() : - VectorBaseFragment() { + VectorBaseFragment() { override fun getBinding(inflater: LayoutInflater, container: ViewGroup?): FragmentSsssResetAllBinding { return FragmentSsssResetAllBinding.inflate(inflater, container, false) diff --git a/vector/src/main/java/im/vector/app/features/crypto/recover/BackupToQuadSMigrationTask.kt b/vector/src/main/java/im/vector/app/features/crypto/recover/BackupToQuadSMigrationTask.kt index 1767a9444d..e620500d70 100644 --- a/vector/src/main/java/im/vector/app/features/crypto/recover/BackupToQuadSMigrationTask.kt +++ b/vector/src/main/java/im/vector/app/features/crypto/recover/BackupToQuadSMigrationTask.kt @@ -20,7 +20,6 @@ import im.vector.app.R import im.vector.app.core.platform.ViewModelTask import im.vector.app.core.platform.WaitingViewData import im.vector.app.core.resources.StringProvider -import org.matrix.android.sdk.api.NoOpMatrixCallback import org.matrix.android.sdk.api.listeners.ProgressListener import org.matrix.android.sdk.api.session.Session import org.matrix.android.sdk.api.session.crypto.crosssigning.KEYBACKUP_SECRET_SSSS_NAME @@ -61,7 +60,7 @@ class BackupToQuadSMigrationTask @Inject constructor( // We need to use the current secret for keybackup and use it as the new master key for SSSS // Then we need to put back the backup key in sss val keysBackupService = session.cryptoService().keysBackupService() - val quadS = session.sharedSecretStorageService + val quadS = session.sharedSecretStorageService() val version = keysBackupService.keysBackupVersion ?: return Result.NoKeyBackupVersion @@ -77,10 +76,14 @@ class BackupToQuadSMigrationTask @Inject constructor( authData.privateKeyIterations!!, object : ProgressListener { override fun onProgress(progress: Int, total: Int) { - params.progressListener?.onProgress(WaitingViewData( - stringProvider.getString(R.string.bootstrap_progress_checking_backup_with_info, - "$progress/$total") - )) + params.progressListener?.onProgress( + WaitingViewData( + stringProvider.getString( + R.string.bootstrap_progress_checking_backup_with_info, + "$progress/$total" + ) + ) + ) } }) } @@ -111,8 +114,10 @@ class BackupToQuadSMigrationTask @Inject constructor( WaitingViewData( stringProvider.getString( R.string.bootstrap_progress_generating_ssss_with_info, - "$progress/$total") - )) + "$progress/$total" + ) + ) + ) } } ) @@ -143,15 +148,8 @@ class BackupToQuadSMigrationTask @Inject constructor( // save for gossiping keysBackupService.saveBackupRecoveryKey(recoveryKey, version.version) - // while we are there let's restore, but do not block - session.cryptoService().keysBackupService().restoreKeysWithRecoveryKey( - version, - recoveryKey, - null, - null, - null, - NoOpMatrixCallback() - ) + // It's not a good idea to download the full backup, it might take very long + // and use a lot of resources return Result.Success } catch (failure: Throwable) { diff --git a/vector/src/main/java/im/vector/app/features/crypto/recover/BootstrapActions.kt b/vector/src/main/java/im/vector/app/features/crypto/recover/BootstrapActions.kt index 363416b7de..395b4d0475 100644 --- a/vector/src/main/java/im/vector/app/features/crypto/recover/BootstrapActions.kt +++ b/vector/src/main/java/im/vector/app/features/crypto/recover/BootstrapActions.kt @@ -37,7 +37,7 @@ sealed class BootstrapActions : VectorViewModelAction { data class UpdateCandidatePassphrase(val pass: String) : BootstrapActions() data class UpdateConfirmCandidatePassphrase(val pass: String) : BootstrapActions() -// data class ReAuth(val pass: String) : BootstrapActions() + // data class ReAuth(val pass: String) : BootstrapActions() object RecoveryKeySaved : BootstrapActions() object Completed : BootstrapActions() object SaveReqQueryStarted : BootstrapActions() diff --git a/vector/src/main/java/im/vector/app/features/crypto/recover/BootstrapBottomSheet.kt b/vector/src/main/java/im/vector/app/features/crypto/recover/BootstrapBottomSheet.kt index ac7662ca59..b8d168cca5 100644 --- a/vector/src/main/java/im/vector/app/features/crypto/recover/BootstrapBottomSheet.kt +++ b/vector/src/main/java/im/vector/app/features/crypto/recover/BootstrapBottomSheet.kt @@ -64,7 +64,7 @@ class BootstrapBottomSheet : VectorBaseBottomSheetDialogFragment if (activityResult.resultCode == Activity.RESULT_OK) { when (activityResult.data?.extras?.getString(ReAuthActivity.RESULT_FLOW_TYPE)) { - LoginFlowTypes.SSO -> { + LoginFlowTypes.SSO -> { viewModel.handle(BootstrapActions.SsoAuthDone) } LoginFlowTypes.PASSWORD -> { @@ -102,10 +102,12 @@ class BootstrapBottomSheet : VectorBaseBottomSheetDialogFragment { - ReAuthActivity.newIntent(requireContext(), + ReAuthActivity.newIntent( + requireContext(), event.flowResponse, event.lastErrorCode, - getString(R.string.initialize_cross_signing)).let { intent -> + getString(R.string.initialize_cross_signing) + ).let { intent -> reAuthActivityResultLauncher.launch(intent) } } @@ -201,9 +203,11 @@ class BootstrapBottomSheet : VectorBaseBottomSheetDialogFragment, argsParcelable: Parcelable? = null) { if (childFragmentManager.findFragmentByTag(fragmentClass.simpleName) == null) { childFragmentManager.commitTransaction { - replace(R.id.bottomSheetFragmentContainer, + replace( + R.id.bottomSheetFragmentContainer, fragmentClass.java, argsParcelable?.toMvRxBundle(), fragmentClass.simpleName diff --git a/vector/src/main/java/im/vector/app/features/crypto/recover/BootstrapConfirmPassphraseFragment.kt b/vector/src/main/java/im/vector/app/features/crypto/recover/BootstrapConfirmPassphraseFragment.kt index 8a211388ed..7920ab3b96 100644 --- a/vector/src/main/java/im/vector/app/features/crypto/recover/BootstrapConfirmPassphraseFragment.kt +++ b/vector/src/main/java/im/vector/app/features/crypto/recover/BootstrapConfirmPassphraseFragment.kt @@ -37,7 +37,7 @@ import reactivecircus.flowbinding.android.widget.textChanges import javax.inject.Inject class BootstrapConfirmPassphraseFragment @Inject constructor() : - VectorBaseFragment() { + VectorBaseFragment() { override fun getBinding(inflater: LayoutInflater, container: ViewGroup?): FragmentBootstrapEnterPassphraseBinding { return FragmentBootstrapEnterPassphraseBinding.inflate(inflater, container, false) diff --git a/vector/src/main/java/im/vector/app/features/crypto/recover/BootstrapCrossSigningTask.kt b/vector/src/main/java/im/vector/app/features/crypto/recover/BootstrapCrossSigningTask.kt index f65999b945..c26aec45dd 100644 --- a/vector/src/main/java/im/vector/app/features/crypto/recover/BootstrapCrossSigningTask.kt +++ b/vector/src/main/java/im/vector/app/features/crypto/recover/BootstrapCrossSigningTask.kt @@ -117,12 +117,13 @@ class BootstrapCrossSigningTask @Inject constructor( val keyInfo: SsssKeyCreationInfo - val ssssService = session.sharedSecretStorageService + val ssssService = session.sharedSecretStorageService() params.progressListener?.onProgress( WaitingViewData( stringProvider.getString(R.string.bootstrap_crosssigning_progress_pbkdf2), - isIndeterminate = true) + isIndeterminate = true + ) ) Timber.d("## BootstrapCrossSigningTask: Creating 4S key with pass: ${params.passphrase != null}") @@ -151,7 +152,8 @@ class BootstrapCrossSigningTask @Inject constructor( params.progressListener?.onProgress( WaitingViewData( stringProvider.getString(R.string.bootstrap_crosssigning_progress_default_key), - isIndeterminate = true) + isIndeterminate = true + ) ) Timber.d("## BootstrapCrossSigningTask: Creating 4S - Set default key") diff --git a/vector/src/main/java/im/vector/app/features/crypto/recover/BootstrapEnterPassphraseFragment.kt b/vector/src/main/java/im/vector/app/features/crypto/recover/BootstrapEnterPassphraseFragment.kt index 51430ba12e..ff6d109b3c 100644 --- a/vector/src/main/java/im/vector/app/features/crypto/recover/BootstrapEnterPassphraseFragment.kt +++ b/vector/src/main/java/im/vector/app/features/crypto/recover/BootstrapEnterPassphraseFragment.kt @@ -36,7 +36,7 @@ import reactivecircus.flowbinding.android.widget.textChanges import javax.inject.Inject class BootstrapEnterPassphraseFragment @Inject constructor() : - VectorBaseFragment() { + VectorBaseFragment() { override fun getBinding(inflater: LayoutInflater, container: ViewGroup?): FragmentBootstrapEnterPassphraseBinding { return FragmentBootstrapEnterPassphraseBinding.inflate(inflater, container, false) diff --git a/vector/src/main/java/im/vector/app/features/crypto/recover/BootstrapSetupRecoveryKeyFragment.kt b/vector/src/main/java/im/vector/app/features/crypto/recover/BootstrapSetupRecoveryKeyFragment.kt index 2765dfefd3..3be2f020b8 100644 --- a/vector/src/main/java/im/vector/app/features/crypto/recover/BootstrapSetupRecoveryKeyFragment.kt +++ b/vector/src/main/java/im/vector/app/features/crypto/recover/BootstrapSetupRecoveryKeyFragment.kt @@ -29,7 +29,7 @@ import im.vector.app.databinding.FragmentBootstrapSetupRecoveryBinding import javax.inject.Inject class BootstrapSetupRecoveryKeyFragment @Inject constructor() : - VectorBaseFragment() { + VectorBaseFragment() { override fun getBinding(inflater: LayoutInflater, container: ViewGroup?): FragmentBootstrapSetupRecoveryBinding { return FragmentBootstrapSetupRecoveryBinding.inflate(inflater, container, false) diff --git a/vector/src/main/java/im/vector/app/features/crypto/recover/BootstrapSharedViewModel.kt b/vector/src/main/java/im/vector/app/features/crypto/recover/BootstrapSharedViewModel.kt index 1b69b3a919..b67970e61f 100644 --- a/vector/src/main/java/im/vector/app/features/crypto/recover/BootstrapSharedViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/crypto/recover/BootstrapSharedViewModel.kt @@ -246,7 +246,8 @@ class BootstrapSharedViewModel @AssistedInject constructor( uiaContinuation?.resume(DefaultBaseAuth(session = pendingAuth?.session ?: "")) } is BootstrapActions.PasswordAuthDone -> { - val decryptedPass = session.loadSecureSecret(action.password.fromBase64().inputStream(), ReAuthActivity.DEFAULT_RESULT_KEYSTORE_ALIAS) + val decryptedPass = session.secureStorageService() + .loadSecureSecret(action.password.fromBase64().inputStream(), ReAuthActivity.DEFAULT_RESULT_KEYSTORE_ALIAS) uiaContinuation?.resume( UserPasswordAuth( session = pendingAuth?.session, @@ -403,7 +404,8 @@ class BootstrapSharedViewModel @AssistedInject constructor( } viewModelScope.launch(Dispatchers.IO) { - bootstrapTask.invoke(this, + bootstrapTask.invoke( + this, Params( userInteractiveAuthInterceptor = interceptor, progressListener = progressListener, diff --git a/vector/src/main/java/im/vector/app/features/crypto/recover/BootstrapWaitingFragment.kt b/vector/src/main/java/im/vector/app/features/crypto/recover/BootstrapWaitingFragment.kt index 8cf48a7c66..cc566833d8 100644 --- a/vector/src/main/java/im/vector/app/features/crypto/recover/BootstrapWaitingFragment.kt +++ b/vector/src/main/java/im/vector/app/features/crypto/recover/BootstrapWaitingFragment.kt @@ -26,7 +26,7 @@ import im.vector.app.databinding.FragmentBootstrapWaitingBinding import javax.inject.Inject class BootstrapWaitingFragment @Inject constructor() : - VectorBaseFragment() { + VectorBaseFragment() { override fun getBinding(inflater: LayoutInflater, container: ViewGroup?): FragmentBootstrapWaitingBinding { return FragmentBootstrapWaitingBinding.inflate(inflater, container, false) diff --git a/vector/src/main/java/im/vector/app/features/crypto/recover/SetupMode.kt b/vector/src/main/java/im/vector/app/features/crypto/recover/SetupMode.kt index 0879490e79..c8bd2e69e5 100644 --- a/vector/src/main/java/im/vector/app/features/crypto/recover/SetupMode.kt +++ b/vector/src/main/java/im/vector/app/features/crypto/recover/SetupMode.kt @@ -19,7 +19,7 @@ package im.vector.app.features.crypto.recover enum class SetupMode { /** - * Only setup cross signing, no 4S or megolm backup + * Only setup cross signing, no 4S or megolm backup. */ CROSS_SIGNING_ONLY, @@ -30,21 +30,21 @@ enum class SetupMode { /** * Only reset the 4S passphrase/key, but do not touch - * to existing cross-signing or megolm backup - * It take the local known secrets and put them in 4S + * to existing cross-signing or megolm backup. + * It takes the local known secrets and put them in 4S. */ PASSPHRASE_RESET, /** * Resets the passphrase/key, and all missing secrets * are re-created. Meaning that if cross signing is setup and the secrets - * keys are not known, cross signing will be reset (if secret is known we just keep same cross signing) - * Same apply to megolm + * keys are not known, cross signing will be reset (if secret is known we just keep same cross signing). + * Same apply to megolm. */ PASSPHRASE_AND_NEEDED_SECRETS_RESET, /** - * Resets the passphrase/key, cross signing and megolm backup + * Resets the passphrase/key, cross signing and megolm backup. */ HARD_RESET } diff --git a/vector/src/main/java/im/vector/app/features/crypto/verification/IncomingVerificationRequestHandler.kt b/vector/src/main/java/im/vector/app/features/crypto/verification/IncomingVerificationRequestHandler.kt index 3a3f1054f1..e0aa4592a4 100644 --- a/vector/src/main/java/im/vector/app/features/crypto/verification/IncomingVerificationRequestHandler.kt +++ b/vector/src/main/java/im/vector/app/features/crypto/verification/IncomingVerificationRequestHandler.kt @@ -18,6 +18,8 @@ package im.vector.app.features.crypto.verification import android.content.Context import im.vector.app.R import im.vector.app.core.platform.VectorBaseActivity +import im.vector.app.core.time.Clock +import im.vector.app.features.analytics.plan.ViewRoom import im.vector.app.features.displayname.getBestName import im.vector.app.features.home.AvatarRenderer import im.vector.app.features.home.room.detail.RoomDetailActivity @@ -42,7 +44,9 @@ import javax.inject.Singleton class IncomingVerificationRequestHandler @Inject constructor( private val context: Context, private var avatarRenderer: Provider, - private val popupAlertManager: PopupAlertManager) : VerificationService.Listener { + private val popupAlertManager: PopupAlertManager, + private val clock: Clock, +) : VerificationService.Listener { private var session: Session? = null @@ -61,9 +65,9 @@ class IncomingVerificationRequestHandler @Inject constructor( // TODO maybe check also if val uid = "kvr_${tx.transactionId}" when (tx.state) { - is VerificationTxState.OnStarted -> { + is VerificationTxState.OnStarted -> { // Add a notification for every incoming request - val user = session?.getUser(tx.otherUserId) + val user = session?.userService()?.getUser(tx.otherUserId) val name = user?.toMatrixItem()?.getBestName() ?: tx.otherUserId val alert = VerificationVectorAlert( uid, @@ -104,7 +108,7 @@ class IncomingVerificationRequestHandler @Inject constructor( } ) // 10mn expiration - expirationTimestamp = System.currentTimeMillis() + (10 * 60 * 1000L) + expirationTimestamp = clock.epochMillis() + (10 * 60 * 1000L) } popupAlertManager.postVectorAlert(alert) } @@ -127,7 +131,7 @@ class IncomingVerificationRequestHandler @Inject constructor( // XXX this is a bit hard coded :/ popupAlertManager.cancelAlert("review_login") } - val user = session?.getUser(pr.otherUserId)?.toMatrixItem() + val user = session?.userService()?.getUser(pr.otherUserId)?.toMatrixItem() val name = user?.getBestName() ?: pr.otherUserId val description = if (name == pr.otherUserId) { name @@ -156,19 +160,25 @@ class IncomingVerificationRequestHandler @Inject constructor( if (roomId.isNullOrBlank()) { it.navigator.waitSessionVerification(it) } else { - it.navigator.openRoom(it, roomId, pr.transactionId) + it.navigator.openRoom( + context = it, + roomId = roomId, + eventId = pr.transactionId, + trigger = ViewRoom.Trigger.VerificationRequest + ) } } } dismissedAction = Runnable { - session?.cryptoService()?.verificationService()?.declineVerificationRequestInDMs(pr.otherUserId, + session?.cryptoService()?.verificationService()?.declineVerificationRequestInDMs( + pr.otherUserId, pr.transactionId ?: "", pr.roomId ?: "" ) } colorAttribute = R.attr.vctr_notice_secondary // 5mn expiration - expirationTimestamp = System.currentTimeMillis() + (5 * 60 * 1000L) + expirationTimestamp = clock.epochMillis() + (5 * 60 * 1000L) } popupAlertManager.postVectorAlert(alert) } diff --git a/vector/src/main/java/im/vector/app/features/crypto/verification/SupportedVerificationMethodsProvider.kt b/vector/src/main/java/im/vector/app/features/crypto/verification/SupportedVerificationMethodsProvider.kt index 00ba676926..f8e63f0ea5 100644 --- a/vector/src/main/java/im/vector/app/features/crypto/verification/SupportedVerificationMethodsProvider.kt +++ b/vector/src/main/java/im/vector/app/features/crypto/verification/SupportedVerificationMethodsProvider.kt @@ -26,14 +26,15 @@ class SupportedVerificationMethodsProvider @Inject constructor( ) { /** * Provide the list of supported method by Element, with or without the QR_CODE_SCAN, depending if a back camera - * is available + * is available. */ fun provide(): List { return mutableListOf( // Element supports SAS verification VerificationMethod.SAS, // Element is able to show QR codes - VerificationMethod.QR_CODE_SHOW) + VerificationMethod.QR_CODE_SHOW + ) .apply { if (hardwareInfo.hasBackCamera()) { // Element is able to scan QR codes, and a Camera is available diff --git a/vector/src/main/java/im/vector/app/features/crypto/verification/VerificationBottomSheet.kt b/vector/src/main/java/im/vector/app/features/crypto/verification/VerificationBottomSheet.kt index 18a1363d71..dc79136cad 100644 --- a/vector/src/main/java/im/vector/app/features/crypto/verification/VerificationBottomSheet.kt +++ b/vector/src/main/java/im/vector/app/features/crypto/verification/VerificationBottomSheet.kt @@ -95,7 +95,7 @@ class VerificationBottomSheet : VectorBaseBottomSheetDialogFragment dismiss() is VerificationBottomSheetViewEvents.AccessSecretStore -> { - secretStartForActivityResult.launch(SharedSecureStorageActivity.newIntent( + secretStartForActivityResult.launch(SharedSecureStorageActivity.newReadIntent( requireContext(), null, // use default key listOf(MASTER_KEY_SSSS_NAME, USER_SIGNING_KEY_SSSS_NAME, SELF_SIGNING_KEY_SSSS_NAME, KEYBACKUP_SECRET_SSSS_NAME), @@ -183,7 +183,8 @@ class VerificationBottomSheet : VectorBaseBottomSheetDialogFragment, argsParcelable: Parcelable? = null) { if (childFragmentManager.findFragmentByTag(fragmentClass.simpleName) == null) { childFragmentManager.commitTransaction { - replace(R.id.bottomSheetFragmentContainer, + replace( + R.id.bottomSheetFragmentContainer, fragmentClass.java, argsParcelable?.toMvRxBundle(), fragmentClass.simpleName @@ -360,31 +362,37 @@ class VerificationBottomSheet : VectorBaseBottomSheetDialogFragment val otherUserId = state.otherUserMxItem?.id ?: return@withState val roomId = state.roomId - ?: session.getExistingDirectRoomWithUser(otherUserId) + ?: session.roomService().getExistingDirectRoomWithUser(otherUserId) when (action) { is VerificationAction.RequestVerificationByDM -> { @@ -244,7 +244,7 @@ class VerificationBottomSheetViewModel @AssistedInject constructor( ) } viewModelScope.launch { - val result = runCatching { session.createDirectRoom(otherUserId) } + val result = runCatching { session.roomService().createDirectRoom(otherUserId) } result.fold( { data -> setState { @@ -274,10 +274,11 @@ class VerificationBottomSheetViewModel @AssistedInject constructor( } else { setState { copy( - pendingRequest = Success(session - .cryptoService() - .verificationService() - .requestKeyVerificationInDMs(supportedVerificationMethodsProvider.provide(), otherUserId, roomId) + pendingRequest = Success( + session + .cryptoService() + .verificationService() + .requestKeyVerificationInDMs(supportedVerificationMethodsProvider.provide(), otherUserId, roomId) ) ) } @@ -372,7 +373,7 @@ class VerificationBottomSheetViewModel @AssistedInject constructor( viewModelScope.launch(Dispatchers.IO) { try { action.cypherData.fromBase64().inputStream().use { ins -> - val res = session.loadSecureSecret>(ins, action.alias) + val res = session.secureStorageService().loadSecureSecret>(ins, action.alias) val trustResult = session.cryptoService().crossSigningService().checkTrustFromPrivateKeys( res?.get(MASTER_KEY_SSSS_NAME), res?.get(USER_SIGNING_KEY_SSSS_NAME), @@ -415,12 +416,18 @@ class VerificationBottomSheetViewModel @AssistedInject constructor( ) } _viewEvents.post( - VerificationBottomSheetViewEvents.ModalError(failure.localizedMessage ?: stringProvider.getString(R.string.unexpected_error))) + VerificationBottomSheetViewEvents.ModalError(failure.localizedMessage ?: stringProvider.getString(R.string.unexpected_error)) + ) } } } private fun tentativeRestoreBackup(res: Map?) { + // It's not a good idea to download the full backup, it might take very long + // and use a lot of resources + // Just check that the key is valid and store it, the backup will be used megolm session per + // megolm session when an UISI is encountered + viewModelScope.launch(Dispatchers.IO) { try { val secret = res?.get(KEYBACKUP_SECRET_SSSS_NAME) ?: return@launch Unit.also { @@ -431,17 +438,13 @@ class VerificationBottomSheetViewModel @AssistedInject constructor( session.cryptoService().keysBackupService().getCurrentVersion(it) }.toKeysVersionResult() ?: return@launch - awaitCallback { - session.cryptoService().keysBackupService().restoreKeysWithRecoveryKey( - version, - computeRecoveryKey(secret.fromBase64()), - null, - null, - null, - it - ) + val recoveryKey = computeRecoveryKey(secret.fromBase64()) + val isValid = awaitCallback { + session.cryptoService().keysBackupService().isValidRecoveryKeyForCurrentVersion(recoveryKey, it) + } + if (isValid) { + session.cryptoService().keysBackupService().saveBackupRecoveryKey(recoveryKey, version.version) } - awaitCallback { session.cryptoService().keysBackupService().trustKeysBackupVersion(version, true, it) } diff --git a/vector/src/main/java/im/vector/app/features/crypto/verification/choose/VerificationChooseMethodFragment.kt b/vector/src/main/java/im/vector/app/features/crypto/verification/choose/VerificationChooseMethodFragment.kt index 31a7956db3..cf6bcc58c0 100644 --- a/vector/src/main/java/im/vector/app/features/crypto/verification/choose/VerificationChooseMethodFragment.kt +++ b/vector/src/main/java/im/vector/app/features/crypto/verification/choose/VerificationChooseMethodFragment.kt @@ -74,9 +74,12 @@ class VerificationChooseMethodFragment @Inject constructor( } override fun doVerifyBySas() = withState(sharedViewModel) { state -> - sharedViewModel.handle(VerificationAction.StartSASVerification( - state.otherUserMxItem?.id ?: "", - state.pendingRequest.invoke()?.transactionId ?: "")) + sharedViewModel.handle( + VerificationAction.StartSASVerification( + state.otherUserMxItem?.id ?: "", + state.pendingRequest.invoke()?.transactionId ?: "" + ) + ) } private val openCameraActivityResultLauncher = registerForPermissionsResult { allGranted, deniedPermanently -> @@ -115,10 +118,12 @@ class VerificationChooseMethodFragment @Inject constructor( } private fun onRemoteQrCodeScanned(remoteQrCode: String) = withState(sharedViewModel) { state -> - sharedViewModel.handle(VerificationAction.RemoteQrCodeScanned( - state.otherUserMxItem?.id ?: "", - state.pendingRequest.invoke()?.transactionId ?: "", - remoteQrCode - )) + sharedViewModel.handle( + VerificationAction.RemoteQrCodeScanned( + state.otherUserMxItem?.id ?: "", + state.pendingRequest.invoke()?.transactionId ?: "", + remoteQrCode + ) + ) } } diff --git a/vector/src/main/java/im/vector/app/features/crypto/verification/choose/VerificationChooseMethodViewModel.kt b/vector/src/main/java/im/vector/app/features/crypto/verification/choose/VerificationChooseMethodViewModel.kt index 7696bb8f5b..a1f902f8f4 100644 --- a/vector/src/main/java/im/vector/app/features/crypto/verification/choose/VerificationChooseMethodViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/crypto/verification/choose/VerificationChooseMethodViewModel.kt @@ -102,7 +102,8 @@ class VerificationChooseMethodViewModel @AssistedInject constructor( // Get the QR code now, because transaction is already created, so transactionCreated() will not be called val qrCodeVerificationTransaction = verificationService.getExistingTransaction(args.otherUserId, args.verificationId ?: "") - return VerificationChooseMethodViewState(otherUserId = args.otherUserId, + return VerificationChooseMethodViewState( + otherUserId = args.otherUserId, isMe = session.myUserId == pvr?.otherUserId, canCrossSign = session.cryptoService().crossSigningService().canCrossSign(), transactionId = args.verificationId ?: "", diff --git a/vector/src/main/java/im/vector/app/features/crypto/verification/conclusion/VerificationConclusionController.kt b/vector/src/main/java/im/vector/app/features/crypto/verification/conclusion/VerificationConclusionController.kt index ee77444b2e..7f6678a73c 100644 --- a/vector/src/main/java/im/vector/app/features/crypto/verification/conclusion/VerificationConclusionController.kt +++ b/vector/src/main/java/im/vector/app/features/crypto/verification/conclusion/VerificationConclusionController.kt @@ -52,10 +52,13 @@ class VerificationConclusionController @Inject constructor( ConclusionState.SUCCESS -> { bottomSheetVerificationNoticeItem { id("notice") - notice(host.stringProvider.getString( - if (state.isSelfVerification) R.string.verification_conclusion_ok_self_notice - else R.string.verification_conclusion_ok_notice) - .toEpoxyCharSequence()) + notice( + host.stringProvider.getString( + if (state.isSelfVerification) R.string.verification_conclusion_ok_self_notice + else R.string.verification_conclusion_ok_notice + ) + .toEpoxyCharSequence() + ) } bottomSheetVerificationBigImageItem { diff --git a/vector/src/main/java/im/vector/app/features/crypto/verification/conclusion/VerificationConclusionViewModel.kt b/vector/src/main/java/im/vector/app/features/crypto/verification/conclusion/VerificationConclusionViewModel.kt index 82bdbccdb3..0e56a6857b 100644 --- a/vector/src/main/java/im/vector/app/features/crypto/verification/conclusion/VerificationConclusionViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/crypto/verification/conclusion/VerificationConclusionViewModel.kt @@ -36,7 +36,7 @@ enum class ConclusionState { } class VerificationConclusionViewModel(initialState: VerificationConclusionViewState) : - VectorViewModel(initialState) { + VectorViewModel(initialState) { companion object : MavericksViewModelFactory { diff --git a/vector/src/main/java/im/vector/app/features/crypto/verification/emoji/VerificationEmojiCodeViewModel.kt b/vector/src/main/java/im/vector/app/features/crypto/verification/emoji/VerificationEmojiCodeViewModel.kt index aec28f898e..95dd4263f9 100644 --- a/vector/src/main/java/im/vector/app/features/crypto/verification/emoji/VerificationEmojiCodeViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/crypto/verification/emoji/VerificationEmojiCodeViewModel.kt @@ -40,6 +40,7 @@ import org.matrix.android.sdk.api.session.crypto.verification.SasVerificationTra import org.matrix.android.sdk.api.session.crypto.verification.VerificationService import org.matrix.android.sdk.api.session.crypto.verification.VerificationTransaction import org.matrix.android.sdk.api.session.crypto.verification.VerificationTxState +import org.matrix.android.sdk.api.session.getUser import org.matrix.android.sdk.api.util.MatrixItem import org.matrix.android.sdk.api.util.toMatrixItem @@ -59,9 +60,13 @@ class VerificationEmojiCodeViewModel @AssistedInject constructor( init { withState { state -> - refreshStateFromTx(session.cryptoService().verificationService() - .getExistingTransaction(state.otherUser?.id ?: "", state.transactionId - ?: "") as? SasVerificationTransaction) + refreshStateFromTx( + session.cryptoService().verificationService() + .getExistingTransaction( + state.otherUser?.id ?: "", state.transactionId + ?: "" + ) as? SasVerificationTransaction + ) } session.cryptoService().verificationService().addListener(this) diff --git a/vector/src/main/java/im/vector/app/features/crypto/verification/epoxy/BottomSheetVerificationQrCodeItem.kt b/vector/src/main/java/im/vector/app/features/crypto/verification/epoxy/BottomSheetVerificationQrCodeItem.kt index 41c92fa76f..0041631986 100644 --- a/vector/src/main/java/im/vector/app/features/crypto/verification/epoxy/BottomSheetVerificationQrCodeItem.kt +++ b/vector/src/main/java/im/vector/app/features/crypto/verification/epoxy/BottomSheetVerificationQrCodeItem.kt @@ -24,7 +24,7 @@ import im.vector.app.core.epoxy.VectorEpoxyModel import im.vector.app.core.ui.views.QrCodeImageView /** - * An Epoxy item displaying a QR code + * An Epoxy item displaying a QR code. */ @EpoxyModelClass(layout = R.layout.item_verification_qr_code) abstract class BottomSheetVerificationQrCodeItem : VectorEpoxyModel() { diff --git a/vector/src/main/java/im/vector/app/features/devtools/RoomDevToolEditFragment.kt b/vector/src/main/java/im/vector/app/features/devtools/RoomDevToolEditFragment.kt index dd0bd174af..1b6fbb7359 100644 --- a/vector/src/main/java/im/vector/app/features/devtools/RoomDevToolEditFragment.kt +++ b/vector/src/main/java/im/vector/app/features/devtools/RoomDevToolEditFragment.kt @@ -32,7 +32,7 @@ import reactivecircus.flowbinding.android.widget.textChanges import javax.inject.Inject class RoomDevToolEditFragment @Inject constructor() : - VectorBaseFragment() { + VectorBaseFragment() { private val sharedViewModel: RoomDevToolViewModel by activityViewModel() diff --git a/vector/src/main/java/im/vector/app/features/devtools/RoomDevToolViewModel.kt b/vector/src/main/java/im/vector/app/features/devtools/RoomDevToolViewModel.kt index c13ef2f81c..fb78f8581a 100644 --- a/vector/src/main/java/im/vector/app/features/devtools/RoomDevToolViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/devtools/RoomDevToolViewModel.kt @@ -36,6 +36,7 @@ import org.matrix.android.sdk.api.session.Session import org.matrix.android.sdk.api.session.events.model.Event import org.matrix.android.sdk.api.session.events.model.EventType import org.matrix.android.sdk.api.session.events.model.toModel +import org.matrix.android.sdk.api.session.getRoom import org.matrix.android.sdk.api.session.room.model.message.MessageContent import org.matrix.android.sdk.api.util.JsonDict import org.matrix.android.sdk.api.util.MatrixJsonParser @@ -173,11 +174,10 @@ class RoomDevToolViewModel @AssistedInject constructor( val json = adapter.fromJson(state.editedContent ?: "") ?: throw IllegalArgumentException(stringProvider.getString(R.string.dev_tools_error_no_content)) - room.sendStateEvent( + room.stateService().sendStateEvent( state.selectedEvent?.type.orEmpty(), state.selectedEvent?.stateKey.orEmpty(), json - ) _viewEvents.post(DevToolsViewEvents.ShowSnackMessage(stringProvider.getString(R.string.dev_tools_success_state_event))) setState { @@ -211,7 +211,7 @@ class RoomDevToolViewModel @AssistedInject constructor( ?: throw IllegalArgumentException(stringProvider.getString(R.string.dev_tools_error_no_message_type)) if (isState) { - room.sendStateEvent( + room.stateService().sendStateEvent( eventType, state.sendEventDraft.stateKey.orEmpty(), json @@ -221,7 +221,7 @@ class RoomDevToolViewModel @AssistedInject constructor( // val validParse = MoshiProvider.providesMoshi().adapter(MessageContent::class.java).fromJson(it.sendEventDraft.content ?: "") json.toModel(catchError = false) ?: throw IllegalArgumentException(stringProvider.getString(R.string.dev_tools_error_malformed_event)) - room.sendEvent( + room.sendService().sendEvent( eventType, json ) diff --git a/vector/src/main/java/im/vector/app/features/discovery/DiscoverySettingsFragment.kt b/vector/src/main/java/im/vector/app/features/discovery/DiscoverySettingsFragment.kt index 2de03f296e..285d0f728f 100644 --- a/vector/src/main/java/im/vector/app/features/discovery/DiscoverySettingsFragment.kt +++ b/vector/src/main/java/im/vector/app/features/discovery/DiscoverySettingsFragment.kt @@ -117,7 +117,8 @@ class DiscoverySettingsFragment @Inject constructor( termsActivityResultLauncher, TermsService.ServiceType.IdentityService, state.identityServer()?.serverUrl?.ensureProtocol() ?: "", - null) + null + ) } } diff --git a/vector/src/main/java/im/vector/app/features/discovery/DiscoverySettingsViewModel.kt b/vector/src/main/java/im/vector/app/features/discovery/DiscoverySettingsViewModel.kt index 8c1caaf67a..47151223a4 100644 --- a/vector/src/main/java/im/vector/app/features/discovery/DiscoverySettingsViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/discovery/DiscoverySettingsViewModel.kt @@ -290,7 +290,7 @@ class DiscoverySettingsViewModel @AssistedInject constructor( } private fun retrieveBinding() { - retrieveBinding(session.getThreePids()) + retrieveBinding(session.profileService().getThreePids()) } private fun retrieveBinding(threePids: List) = withState { state -> diff --git a/vector/src/main/java/im/vector/app/features/discovery/Extensions.kt b/vector/src/main/java/im/vector/app/features/discovery/Extensions.kt index 904b152661..dc25a35646 100644 --- a/vector/src/main/java/im/vector/app/features/discovery/Extensions.kt +++ b/vector/src/main/java/im/vector/app/features/discovery/Extensions.kt @@ -24,7 +24,7 @@ import org.matrix.android.sdk.api.session.terms.TermsService suspend fun Session.fetchIdentityServerWithTerms(userLanguage: String): ServerAndPolicies? { return identityService().getCurrentIdentityServerUrl() ?.let { identityServerUrl -> - val termsResponse = getTerms(TermsService.ServiceType.IdentityService, identityServerUrl.ensureProtocol()) + val termsResponse = termsService().getTerms(TermsService.ServiceType.IdentityService, identityServerUrl.ensureProtocol()) .serverResponse buildServerAndPolicies(identityServerUrl, termsResponse, userLanguage) } @@ -32,7 +32,7 @@ suspend fun Session.fetchIdentityServerWithTerms(userLanguage: String): ServerAn suspend fun Session.fetchHomeserverWithTerms(userLanguage: String): ServerAndPolicies { val homeserverUrl = sessionParams.homeServerUrl - val terms = getHomeserverTerms(homeserverUrl.ensureProtocol()) + val terms = termsService().getHomeserverTerms(homeserverUrl.ensureProtocol()) return buildServerAndPolicies(homeserverUrl, terms, userLanguage) } diff --git a/vector/src/main/java/im/vector/app/features/discovery/change/SetIdentityServerFragment.kt b/vector/src/main/java/im/vector/app/features/discovery/change/SetIdentityServerFragment.kt index ca43e80ea6..fe2be713af 100644 --- a/vector/src/main/java/im/vector/app/features/discovery/change/SetIdentityServerFragment.kt +++ b/vector/src/main/java/im/vector/app/features/discovery/change/SetIdentityServerFragment.kt @@ -67,8 +67,10 @@ class SetIdentityServerFragment @Inject constructor( state.defaultIdentityServerUrl.toReducedUrl() ) .toSpannable() - .colorizeMatchingText(state.defaultIdentityServerUrl.toReducedUrl(), - colorProvider.getColorFromAttribute(R.attr.vctr_content_tertiary)) + .colorizeMatchingText( + state.defaultIdentityServerUrl.toReducedUrl(), + colorProvider.getColorFromAttribute(R.attr.vctr_content_tertiary) + ) views.identityServerSetDefaultNotice.isVisible = true views.identityServerSetDefaultSubmit.isVisible = true @@ -129,7 +131,8 @@ class SetIdentityServerFragment @Inject constructor( termsActivityResultLauncher, TermsService.ServiceType.IdentityService, it.identityServerUrl, - null) + null + ) } } } diff --git a/vector/src/main/java/im/vector/app/features/discovery/change/SetIdentityServerViewModel.kt b/vector/src/main/java/im/vector/app/features/discovery/change/SetIdentityServerViewModel.kt index da01ba5662..ae1c7f7753 100644 --- a/vector/src/main/java/im/vector/app/features/discovery/change/SetIdentityServerViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/discovery/change/SetIdentityServerViewModel.kt @@ -39,7 +39,7 @@ class SetIdentityServerViewModel @AssistedInject constructor( @Assisted initialState: SetIdentityServerState, private val mxSession: Session, stringProvider: StringProvider) : - VectorViewModel(initialState) { + VectorViewModel(initialState) { @AssistedFactory interface Factory : MavericksAssistedViewModelFactory { @@ -107,7 +107,7 @@ class SetIdentityServerViewModel @AssistedInject constructor( private suspend fun checkTerms(baseUrl: String) { try { - val data = mxSession.getTerms(TermsService.ServiceType.IdentityService, baseUrl) + val data = mxSession.termsService().getTerms(TermsService.ServiceType.IdentityService, baseUrl) // has all been accepted? val resp = data.serverResponse diff --git a/vector/src/main/java/im/vector/app/features/form/FormEditTextItem.kt b/vector/src/main/java/im/vector/app/features/form/FormEditTextItem.kt index 7b7896316b..5023c6bd83 100644 --- a/vector/src/main/java/im/vector/app/features/form/FormEditTextItem.kt +++ b/vector/src/main/java/im/vector/app/features/form/FormEditTextItem.kt @@ -128,7 +128,7 @@ abstract class FormEditTextItem : VectorEpoxyModel() { /** * Configure the inputType of the EditText, input type should be always defined * especially when we want to use a single line, we set the InputType to InputType.TYPE_CLASS_TEXT - * while the default for the EditText is InputType.TYPE_CLASS_TEXT or InputType.TYPE_TEXT_FLAG_MULTI_LINE + * while the default for the EditText is InputType.TYPE_CLASS_TEXT or InputType.TYPE_TEXT_FLAG_MULTI_LINE. */ private fun configureInputType(holder: Holder) { val newInputType = @@ -146,7 +146,7 @@ abstract class FormEditTextItem : VectorEpoxyModel() { /** * Configure the imeOptions of the EditText, when imeOptions are not defined by the developer * EditorInfo.IME_ACTION_NEXT will be used for singleLine EditTexts to disable "new line" - * while EditorInfo.IME_ACTION_NONE will be used for all the other cases + * while EditorInfo.IME_ACTION_NONE will be used for all the other cases. */ private fun configureImeOptions(holder: Holder) { holder.textInputEditText.imeOptions = diff --git a/vector/src/main/java/im/vector/app/features/home/AvatarRenderer.kt b/vector/src/main/java/im/vector/app/features/home/AvatarRenderer.kt index 326b350f30..cb24382e6a 100644 --- a/vector/src/main/java/im/vector/app/features/home/AvatarRenderer.kt +++ b/vector/src/main/java/im/vector/app/features/home/AvatarRenderer.kt @@ -53,9 +53,8 @@ import java.io.File import javax.inject.Inject /** - * This helper centralise ways to retrieve avatar into ImageView or even generic Target + * This helper centralise ways to retrieve avatar into ImageView or even generic Target. */ - class AvatarRenderer @Inject constructor(private val activeSessionHolder: ActiveSessionHolder, private val matrixItemColorProvider: MatrixItemColorProvider, private val dimensionConverter: DimensionConverter) { @@ -66,9 +65,11 @@ class AvatarRenderer @Inject constructor(private val activeSessionHolder: Active @UiThread fun render(matrixItem: MatrixItem, imageView: ImageView) { - render(GlideApp.with(imageView), + render( + GlideApp.with(imageView), matrixItem, - DrawableImageViewTarget(imageView)) + DrawableImageViewTarget(imageView) + ) } // fun renderSpace(matrixItem: MatrixItem, imageView: ImageView) { @@ -97,9 +98,11 @@ class AvatarRenderer @Inject constructor(private val activeSessionHolder: Active @UiThread fun render(matrixItem: MatrixItem, imageView: ImageView, glideRequests: GlideRequests) { - render(glideRequests, + render( + glideRequests, matrixItem, - DrawableImageViewTarget(imageView)) + DrawableImageViewTarget(imageView) + ) } @UiThread @@ -200,12 +203,14 @@ class AvatarRenderer @Inject constructor(private val activeSessionHolder: Active it.load(resolvedUrl) } else { val avatarColor = matrixItemColorProvider.getColor(matrixItem) - it.load(TextDrawable.builder() - .beginConfig() - .bold() - .endConfig() - .buildRect(matrixItem.firstLetterOfDisplayName(), avatarColor) - .toBitmap(width = iconSize, height = iconSize)) + it.load( + TextDrawable.builder() + .beginConfig() + .bold() + .endConfig() + .buildRect(matrixItem.firstLetterOfDisplayName(), avatarColor) + .toBitmap(width = iconSize, height = iconSize) + ) } } } 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 7a8dcf2634..ee9340f37d 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 @@ -49,8 +49,10 @@ import im.vector.app.features.MainActivity import im.vector.app.features.MainActivityArgs import im.vector.app.features.analytics.accountdata.AnalyticsAccountDataViewModel import im.vector.app.features.analytics.plan.MobileScreen +import im.vector.app.features.analytics.plan.ViewRoom import im.vector.app.features.disclaimer.showDisclaimerDialog import im.vector.app.features.matrixto.MatrixToBottomSheet +import im.vector.app.features.matrixto.OriginOfMatrixTo import im.vector.app.features.navigation.Navigator import im.vector.app.features.notifications.NotificationDrawerManager import im.vector.app.features.permalink.NavigationInterceptor @@ -140,9 +142,11 @@ class HomeActivity : } // Here we want to change current space to the newly created one, and then immediately open the default room if (spaceId != null) { - navigator.switchToSpace(context = this, + navigator.switchToSpace( + context = this, spaceId = spaceId, - postSwitchOption) + postSwitchOption + ) } } } @@ -243,7 +247,7 @@ class HomeActivity : } if (args?.inviteNotificationRoomId != null) { activeSessionHolder.getSafeActiveSession()?.permalinkService()?.createPermalink(args.inviteNotificationRoomId)?.let { - navigator.openMatrixToBottomSheet(this, it) + navigator.openMatrixToBottomSheet(this, it, OriginOfMatrixTo.NOTIFICATION) } } @@ -274,7 +278,7 @@ class HomeActivity : /** * Migrating from old threads io.element.thread to new m.thread needs an initial sync to - * sync and display existing messages appropriately + * sync and display existing messages appropriately. */ private fun migrateThreadsIfNeeded(checkSession: Boolean) { if (checkSession) { @@ -297,7 +301,7 @@ class HomeActivity : } /** - * Clear cache and restart to invoke an initial sync for threads migration + * Clear cache and restart to invoke an initial sync for threads migration. */ private fun handleThreadsMigration() { Timber.i("----> Threads Migration detected, clearing cache and sync...") @@ -370,7 +374,7 @@ class HomeActivity : } views.waitingView.root.isVisible = true } - else -> { + else -> { // Idle or Incremental sync status views.waitingView.root.isVisible = false } @@ -480,7 +484,7 @@ class HomeActivity : activeSessionHolder.getSafeActiveSession() ?.permalinkService() ?.createPermalink(parcelableExtra.inviteNotificationRoomId)?.let { - navigator.openMatrixToBottomSheet(this, it) + navigator.openMatrixToBottomSheet(this, it, OriginOfMatrixTo.NOTIFICATION) } } handleIntent(intent) @@ -567,14 +571,14 @@ class HomeActivity : override fun navToMemberProfile(userId: String, deepLink: Uri): Boolean { // TODO check if there is already one?? - MatrixToBottomSheet.withLink(deepLink.toString()) + MatrixToBottomSheet.withLink(deepLink.toString(), OriginOfMatrixTo.LINK) .show(supportFragmentManager, "HA#MatrixToBottomSheet") return true } override fun navToRoom(roomId: String?, eventId: String?, deepLink: Uri?, rootThreadEventId: String?): Boolean { if (roomId == null) return false - MatrixToBottomSheet.withLink(deepLink.toString()) + MatrixToBottomSheet.withLink(deepLink.toString(), OriginOfMatrixTo.LINK) .show(supportFragmentManager, "HA#MatrixToBottomSheet") return true } @@ -608,8 +612,8 @@ class HomeActivity : } } - override fun mxToBottomSheetNavigateToRoom(roomId: String) { - navigator.openRoom(this, roomId) + override fun mxToBottomSheetNavigateToRoom(roomId: String, trigger: ViewRoom.Trigger?) { + navigator.openRoom(this, roomId, trigger = trigger) } override fun mxToBottomSheetSwitchToSpace(spaceId: String) { diff --git a/vector/src/main/java/im/vector/app/features/home/HomeActivitySharedAction.kt b/vector/src/main/java/im/vector/app/features/home/HomeActivitySharedAction.kt index 6047a1e55e..d460efb564 100644 --- a/vector/src/main/java/im/vector/app/features/home/HomeActivitySharedAction.kt +++ b/vector/src/main/java/im/vector/app/features/home/HomeActivitySharedAction.kt @@ -19,7 +19,7 @@ package im.vector.app.features.home import im.vector.app.core.platform.VectorSharedAction /** - * Supported navigation actions for [HomeActivity] + * Supported navigation actions for [HomeActivity]. */ sealed class HomeActivitySharedAction : VectorSharedAction { object OpenDrawer : HomeActivitySharedAction() diff --git a/vector/src/main/java/im/vector/app/features/home/HomeActivityViewModel.kt b/vector/src/main/java/im/vector/app/features/home/HomeActivityViewModel.kt index c8b0fa024e..c47ca0880e 100644 --- a/vector/src/main/java/im/vector/app/features/home/HomeActivityViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/home/HomeActivityViewModel.kt @@ -42,10 +42,11 @@ import org.matrix.android.sdk.api.auth.data.LoginFlowTypes import org.matrix.android.sdk.api.auth.registration.RegistrationFlowResponse import org.matrix.android.sdk.api.auth.registration.nextUncompletedStage import org.matrix.android.sdk.api.extensions.tryOrNull -import org.matrix.android.sdk.api.pushrules.RuleIds import org.matrix.android.sdk.api.session.crypto.model.CryptoDeviceInfo import org.matrix.android.sdk.api.session.crypto.model.MXUsersDevicesMap +import org.matrix.android.sdk.api.session.getUser import org.matrix.android.sdk.api.session.initsync.SyncStatusService +import org.matrix.android.sdk.api.session.pushrules.RuleIds import org.matrix.android.sdk.api.session.room.model.Membership import org.matrix.android.sdk.api.session.room.roomSummaryQueryParams import org.matrix.android.sdk.api.settings.LightweightSettingsStorage @@ -175,7 +176,7 @@ class HomeActivityViewModel @AssistedInject constructor( private fun observeInitialSync() { val session = activeSessionHolder.getSafeActiveSession() ?: return - session.getSyncStatusLive() + session.syncStatusService().getSyncStatusLive() .asFlow() .onEach { status -> when (status) { @@ -189,7 +190,7 @@ class HomeActivityViewModel @AssistedInject constructor( maybeBootstrapCrossSigningAfterInitialSync() } } - else -> Unit + else -> Unit } setState { @@ -203,9 +204,9 @@ class HomeActivityViewModel @AssistedInject constructor( /** * After migration from riot to element some users reported that their - * push setting for the session was set to off + * push setting for the session was set to off. * In order to mitigate this, we want to display a popup once to the user - * giving him the option to review this setting + * giving him the option to review this setting. */ private fun checkSessionPushIsOn() { viewModelScope.launch(Dispatchers.IO) { @@ -215,15 +216,18 @@ class HomeActivityViewModel @AssistedInject constructor( if (!vectorPreferences.areNotificationEnabledForDevice()) { // Check if set at account level val mRuleMaster = activeSessionHolder.getSafeActiveSession() + ?.pushRuleService() ?.getPushRules() ?.getAllRules() ?.find { it.ruleId == RuleIds.RULE_ID_DISABLE_ALL } if (mRuleMaster?.enabled == false) { // So push are enabled at account level but not for this session // Let's check that there are some rooms? - val knownRooms = activeSessionHolder.getSafeActiveSession()?.getRoomSummaries(roomSummaryQueryParams { - memberships = Membership.activeMemberships() - })?.size ?: 0 + val knownRooms = activeSessionHolder.getSafeActiveSession() + ?.roomService() + ?.getRoomSummaries(roomSummaryQueryParams { + memberships = Membership.activeMemberships() + })?.size ?: 0 // Prompt once to the user if (knownRooms > 1 && !vectorPreferences.didAskUserToEnableSessionPush()) { diff --git a/vector/src/main/java/im/vector/app/features/home/HomeDetailFragment.kt b/vector/src/main/java/im/vector/app/features/home/HomeDetailFragment.kt index f3973e5d4c..2753ba817d 100644 --- a/vector/src/main/java/im/vector/app/features/home/HomeDetailFragment.kt +++ b/vector/src/main/java/im/vector/app/features/home/HomeDetailFragment.kt @@ -444,7 +444,8 @@ class HomeDetailFragment @Inject constructor( it.syncState, it.incrementalSyncStatus, it.pushCounter, - vectorPreferences.developerShowDebugInfo()) + vectorPreferences.developerShowDebugInfo() + ) hasUnreadRooms = it.hasUnreadMessages } diff --git a/vector/src/main/java/im/vector/app/features/home/HomeDetailViewModel.kt b/vector/src/main/java/im/vector/app/features/home/HomeDetailViewModel.kt index 2457dd0eaa..a4f1566eff 100644 --- a/vector/src/main/java/im/vector/app/features/home/HomeDetailViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/home/HomeDetailViewModel.kt @@ -59,7 +59,7 @@ import timber.log.Timber /** * View model used to update the home bottom bar notification counts, observe the sync state and - * change the selected room list view + * change the selected room list view. */ class HomeDetailViewModel @AssistedInject constructor( @Assisted initialState: HomeDetailViewState, @@ -91,7 +91,7 @@ class HomeDetailViewModel @AssistedInject constructor( private val refreshRoomSummariesOnCryptoSessionChange = object : NewSessionListener { override fun onNewSession(roomId: String?, senderKey: String, sessionId: String) { - session.refreshJoinedRoomSummaryPreviews(roomId) + session.roomService().refreshJoinedRoomSummaryPreviews(roomId) } } @@ -176,7 +176,7 @@ class HomeDetailViewModel @AssistedInject constructor( private fun handleMarkAllRoomsRead() = withState { _ -> // questionable to use viewmodelscope viewModelScope.launch(Dispatchers.Default) { - val roomIds = session.getRoomSummaries( + val roomIds = session.roomService().getRoomSummaries( roomSummaryQueryParams { memberships = listOf(Membership.JOIN) roomCategoryFilter = RoomCategoryFilter.ONLY_WITH_NOTIFICATIONS @@ -184,7 +184,7 @@ class HomeDetailViewModel @AssistedInject constructor( ) .map { it.roomId } try { - session.markAllAsRead(roomIds) + session.roomService().markAllAsRead(roomIds) } catch (failure: Throwable) { Timber.d(failure, "Failed to mark all as read") } @@ -198,7 +198,7 @@ class HomeDetailViewModel @AssistedInject constructor( copy(syncState = syncState) } - session.getSyncStatusLive() + session.syncStatusService().getSyncStatusLive() .asFlow() .filterIsInstance() .setOnEach { @@ -219,7 +219,7 @@ class HomeDetailViewModel @AssistedInject constructor( appStateHandler.selectedRoomGroupingFlow.distinctUntilChanged().flatMapLatest { // we use it as a trigger to all changes in room, but do not really load // the actual models - session.getPagedRoomSummariesLive( + session.roomService().getPagedRoomSummariesLive( roomSummaryQueryParams { memberships = Membership.activeMemberships() }, @@ -237,7 +237,7 @@ class HomeDetailViewModel @AssistedInject constructor( var dmInvites = 0 var roomsInvite = 0 if (autoAcceptInvites.showInvites()) { - dmInvites = session.getRoomSummaries( + dmInvites = session.roomService().getRoomSummaries( roomSummaryQueryParams { memberships = listOf(Membership.INVITE) roomCategoryFilter = RoomCategoryFilter.ONLY_DM @@ -245,7 +245,7 @@ class HomeDetailViewModel @AssistedInject constructor( } ).size - roomsInvite = session.getRoomSummaries( + roomsInvite = session.roomService().getRoomSummaries( roomSummaryQueryParams { memberships = listOf(Membership.INVITE) roomCategoryFilter = RoomCategoryFilter.ONLY_ROOMS @@ -254,7 +254,7 @@ class HomeDetailViewModel @AssistedInject constructor( ).size } - val dmRooms = session.getNotificationCountForRooms( + val dmRooms = session.roomService().getNotificationCountForRooms( roomSummaryQueryParams { memberships = listOf(Membership.JOIN) roomCategoryFilter = RoomCategoryFilter.ONLY_DM @@ -262,7 +262,7 @@ class HomeDetailViewModel @AssistedInject constructor( } ) - val otherRooms = session.getNotificationCountForRooms( + val otherRooms = session.roomService().getNotificationCountForRooms( roomSummaryQueryParams { memberships = listOf(Membership.JOIN) roomCategoryFilter = RoomCategoryFilter.ONLY_ROOMS diff --git a/vector/src/main/java/im/vector/app/features/home/HomeDrawerFragment.kt b/vector/src/main/java/im/vector/app/features/home/HomeDrawerFragment.kt index 1aee0257f4..9ce950ba31 100644 --- a/vector/src/main/java/im/vector/app/features/home/HomeDrawerFragment.kt +++ b/vector/src/main/java/im/vector/app/features/home/HomeDrawerFragment.kt @@ -60,7 +60,7 @@ class HomeDrawerFragment @Inject constructor( if (savedInstanceState == null) { replaceChildFragment(R.id.homeDrawerGroupListContainer, SpaceListFragment::class.java) } - session.getUserLive(session.myUserId).observeK(viewLifecycleOwner) { optionalUser -> + session.userService().getUserLive(session.myUserId).observeK(viewLifecycleOwner) { optionalUser -> val user = optionalUser?.getOrNull() if (user != null) { avatarRenderer.render(user.toMatrixItem(), views.homeDrawerHeaderAvatarView) diff --git a/vector/src/main/java/im/vector/app/features/home/UnknownDeviceDetectorSharedViewModel.kt b/vector/src/main/java/im/vector/app/features/home/UnknownDeviceDetectorSharedViewModel.kt index 08b528f25a..34bdc5fcd3 100644 --- a/vector/src/main/java/im/vector/app/features/home/UnknownDeviceDetectorSharedViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/home/UnknownDeviceDetectorSharedViewModel.kt @@ -29,6 +29,7 @@ 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.core.platform.VectorViewModelAction +import im.vector.app.core.time.Clock import im.vector.app.features.settings.VectorPreferences import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.distinctUntilChanged @@ -39,6 +40,7 @@ import org.matrix.android.sdk.api.NoOpMatrixCallback import org.matrix.android.sdk.api.extensions.orFalse import org.matrix.android.sdk.api.session.Session import org.matrix.android.sdk.api.session.crypto.model.DeviceInfo +import org.matrix.android.sdk.api.session.getUser import org.matrix.android.sdk.api.util.MatrixItem import org.matrix.android.sdk.api.util.toMatrixItem import org.matrix.android.sdk.flow.flow @@ -55,10 +57,12 @@ data class DeviceDetectionInfo( val currentSessionTrust: Boolean ) -class UnknownDeviceDetectorSharedViewModel @AssistedInject constructor(@Assisted initialState: UnknownDevicesState, - session: Session, - private val vectorPreferences: VectorPreferences) : - VectorViewModel(initialState) { +class UnknownDeviceDetectorSharedViewModel @AssistedInject constructor( + @Assisted initialState: UnknownDevicesState, + session: Session, + private val vectorPreferences: VectorPreferences, + clock: Clock, +) : VectorViewModel(initialState) { sealed class Action : VectorViewModelAction { data class IgnoreDevice(val deviceIds: List) : Action() @@ -74,11 +78,10 @@ class UnknownDeviceDetectorSharedViewModel @AssistedInject constructor(@Assisted private val ignoredDeviceList = ArrayList() init { - val currentSessionTs = session.cryptoService().getCryptoDeviceInfo(session.myUserId) .firstOrNull { it.deviceId == session.sessionParams.deviceId } ?.firstTimeSeenLocalTs - ?: System.currentTimeMillis() + ?: clock.epochMillis() Timber.v("## Detector - Current Session first time seen $currentSessionTs") ignoredDeviceList.addAll( diff --git a/vector/src/main/java/im/vector/app/features/home/UnreadMessagesSharedViewModel.kt b/vector/src/main/java/im/vector/app/features/home/UnreadMessagesSharedViewModel.kt index 409eb0b845..c1b3937fee 100644 --- a/vector/src/main/java/im/vector/app/features/home/UnreadMessagesSharedViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/home/UnreadMessagesSharedViewModel.kt @@ -71,9 +71,11 @@ class UnreadMessagesSharedViewModel @AssistedInject constructor(@Assisted initia override fun handle(action: EmptyAction) {} + private val roomService = session.roomService() + init { - session.getPagedRoomSummariesLive( + roomService.getPagedRoomSummariesLive( roomSummaryQueryParams { this.memberships = listOf(Membership.JOIN) this.activeSpaceFilter = ActiveSpaceFilter.ActiveSpace(null) @@ -81,7 +83,7 @@ class UnreadMessagesSharedViewModel @AssistedInject constructor(@Assisted initia ).asFlow() .throttleFirst(300) .execute { - val counts = session.getNotificationCountForRooms( + val counts = roomService.getNotificationCountForRooms( roomSummaryQueryParams { this.memberships = listOf(Membership.JOIN) this.activeSpaceFilter = ActiveSpaceFilter.ActiveSpace(null) @@ -90,7 +92,7 @@ class UnreadMessagesSharedViewModel @AssistedInject constructor(@Assisted initia val invites = if (autoAcceptInvites.hideInvites) { 0 } else { - session.getRoomSummaries( + roomService.getRoomSummaries( roomSummaryQueryParams { this.memberships = listOf(Membership.INVITE) this.activeSpaceFilter = ActiveSpaceFilter.ActiveSpace(null) @@ -109,7 +111,7 @@ class UnreadMessagesSharedViewModel @AssistedInject constructor(@Assisted initia combine( appStateHandler.selectedRoomGroupingFlow.distinctUntilChanged(), appStateHandler.selectedRoomGroupingFlow.flatMapLatest { - session.getPagedRoomSummariesLive( + roomService.getPagedRoomSummariesLive( roomSummaryQueryParams { this.memberships = Membership.activeMemberships() }, sortOrder = RoomSortOrder.NONE @@ -131,7 +133,7 @@ class UnreadMessagesSharedViewModel @AssistedInject constructor(@Assisted initia val inviteCount = if (autoAcceptInvites.hideInvites) { 0 } else { - session.getRoomSummaries( + roomService.getRoomSummaries( roomSummaryQueryParams { this.memberships = listOf(Membership.INVITE) } ).size } @@ -139,14 +141,14 @@ class UnreadMessagesSharedViewModel @AssistedInject constructor(@Assisted initia val spaceInviteCount = if (autoAcceptInvites.hideInvites) { 0 } else { - session.getRoomSummaries( + roomService.getRoomSummaries( spaceSummaryQueryParams { this.memberships = listOf(Membership.INVITE) } ).size } - val totalCount = session.getNotificationCountForRooms( + val totalCount = roomService.getNotificationCountForRooms( roomSummaryQueryParams { this.memberships = listOf(Membership.JOIN) this.activeSpaceFilter = ActiveSpaceFilter.ActiveSpace(null).takeIf { diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/AutoCompleter.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/AutoCompleter.kt index be5f9c0bb4..65d18105fe 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/AutoCompleter.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/AutoCompleter.kt @@ -53,7 +53,7 @@ class AutoCompleter @AssistedInject constructor( @Assisted val isInThreadTimeline: Boolean, private val avatarRenderer: AvatarRenderer, private val commandAutocompletePolicy: CommandAutocompletePolicy, - AutocompleteCommandPresenterFactory: AutocompleteCommandPresenter.Factory, + autocompleteCommandPresenterFactory: AutocompleteCommandPresenter.Factory, private val autocompleteMemberPresenterFactory: AutocompleteMemberPresenter.Factory, private val autocompleteRoomPresenter: AutocompleteRoomPresenter, private val autocompleteGroupPresenter: AutocompleteGroupPresenter, @@ -68,7 +68,7 @@ class AutoCompleter @AssistedInject constructor( } private val autocompleteCommandPresenter: AutocompleteCommandPresenter by lazy { - AutocompleteCommandPresenterFactory.create(isInThreadTimeline) + autocompleteCommandPresenterFactory.create(isInThreadTimeline) } private var editText: EditText? = null diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/ChatEffectManager.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/ChatEffectManager.kt index f95baae36b..fa19e39ae7 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/ChatEffectManager.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/ChatEffectManager.kt @@ -16,6 +16,7 @@ package im.vector.app.features.home.room.detail +import im.vector.app.core.time.Clock import org.matrix.android.sdk.api.session.events.model.toModel import org.matrix.android.sdk.api.session.room.model.message.MessageContent import org.matrix.android.sdk.api.session.room.model.message.MessageType @@ -45,7 +46,9 @@ fun ChatEffect.toMessageType(): String { * precisely an event decrypted with a few delay won't trigger an effect; it's acceptable) * Events that are more that 10s old won't trigger effects */ -class ChatEffectManager @Inject constructor() { +class ChatEffectManager @Inject constructor( + private val clock: Clock, +) { interface Delegate { fun stopEffects() @@ -61,7 +64,7 @@ class ChatEffectManager @Inject constructor() { fun checkForEffect(event: TimelineEvent) { val age = event.root.ageLocalTs ?: 0 - val now = System.currentTimeMillis() + val now = clock.epochMillis() // messages older than 10s should not trigger any effect if ((now - age) >= 10_000) return val content = event.root.getClearContent()?.toModel() ?: return diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/JoinReplacementRoomBottomSheet.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/JoinReplacementRoomBottomSheet.kt index 99843084ec..ce2903a6fa 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/JoinReplacementRoomBottomSheet.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/JoinReplacementRoomBottomSheet.kt @@ -62,15 +62,15 @@ class JoinReplacementRoomBottomSheet : when (joinState) { // it should never be Uninitialized Uninitialized, - is Loading -> { + is Loading -> { views.roomUpgradeButton.render(ButtonStateView.State.Loading) views.descriptionText.setText(R.string.it_may_take_some_time) } - is Success -> { + is Success -> { views.roomUpgradeButton.render(ButtonStateView.State.Loaded) dismiss() } - is Fail -> { + is Fail -> { // display the error message views.descriptionText.text = errorFormatter.toHumanReadable(joinState.error) views.roomUpgradeButton.render(ButtonStateView.State.Error) diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailActivity.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailActivity.kt index 5784e6e264..ac4df411a8 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailActivity.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailActivity.kt @@ -36,6 +36,7 @@ import im.vector.app.core.extensions.replaceFragment import im.vector.app.core.platform.VectorBaseActivity import im.vector.app.databinding.ActivityRoomDetailBinding import im.vector.app.features.analytics.plan.MobileScreen +import im.vector.app.features.analytics.plan.ViewRoom import im.vector.app.features.home.room.breadcrumbs.BreadcrumbsFragment import im.vector.app.features.home.room.detail.arguments.TimelineArgs import im.vector.app.features.home.room.detail.timeline.helper.AudioMessagePlaybackTracker @@ -205,8 +206,8 @@ class RoomDetailActivity : } } - override fun mxToBottomSheetNavigateToRoom(roomId: String) { - navigator.openRoom(this, roomId) + override fun mxToBottomSheetNavigateToRoom(roomId: String, trigger: ViewRoom.Trigger?) { + navigator.openRoom(this, roomId, trigger = trigger) } override fun mxToBottomSheetSwitchToSpace(spaceId: String) { diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailSharedAction.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailSharedAction.kt index 1b4435bf57..a72aca31a0 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailSharedAction.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailSharedAction.kt @@ -19,7 +19,7 @@ package im.vector.app.features.home.room.detail import im.vector.app.core.platform.VectorSharedAction /** - * Supported navigation actions for [RoomDetailActivity] + * Supported navigation actions for [RoomDetailActivity]. */ sealed class RoomDetailSharedAction : VectorSharedAction { data class SwitchToRoom(val roomId: String) : RoomDetailSharedAction() diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailSharedActionViewModel.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailSharedActionViewModel.kt index 065be461f3..830134309a 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailSharedActionViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailSharedActionViewModel.kt @@ -19,6 +19,6 @@ import im.vector.app.core.platform.VectorSharedActionViewModel import javax.inject.Inject /** - * Activity shared view model + * Activity shared view model. */ class RoomDetailSharedActionViewModel @Inject constructor() : VectorSharedActionViewModel() diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailViewEvents.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailViewEvents.kt index f36a1141b8..b168bfea97 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailViewEvents.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailViewEvents.kt @@ -26,7 +26,7 @@ import org.matrix.android.sdk.api.util.MatrixItem import java.io.File /** - * Transient events for RoomDetail + * Transient events for RoomDetail. */ sealed class RoomDetailViewEvents : VectorViewEvents { data class Failure(val throwable: Throwable, val showInDialog: Boolean = false) : RoomDetailViewEvents() diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailViewState.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailViewState.kt index 0c56d7e7eb..e01c5ba3b7 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailViewState.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailViewState.kt @@ -87,7 +87,12 @@ data class RoomDetailViewState( rootThreadEventId = args.threadTimelineArgs?.rootThreadEventId ) - fun isCallOptionAvailable() = asyncRoomSummary.invoke()?.isDirect ?: true + fun isCallOptionAvailable(): Boolean { + return asyncRoomSummary.invoke()?.isDirect ?: true || + // When there is only one member, a warning will be displayed when the user + // clicks on the menu item to start a call + asyncRoomSummary.invoke()?.joinedMembersCount == 1 + } fun isSearchAvailable() = asyncRoomSummary()?.isEncrypted == false diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/RoomMessageTouchHelperCallback.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/RoomMessageTouchHelperCallback.kt index 9f10805f95..1f1124b8c0 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/RoomMessageTouchHelperCallback.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/RoomMessageTouchHelperCallback.kt @@ -33,14 +33,18 @@ import com.airbnb.epoxy.EpoxyModel import com.airbnb.epoxy.EpoxyTouchHelperCallback import com.airbnb.epoxy.EpoxyViewHolder import im.vector.app.R +import im.vector.app.core.time.Clock import im.vector.app.features.themes.ThemeUtils import timber.log.Timber import kotlin.math.abs import kotlin.math.min -class RoomMessageTouchHelperCallback(private val context: Context, - @DrawableRes actionIcon: Int, - private val handler: QuickReplayHandler) : EpoxyTouchHelperCallback() { +class RoomMessageTouchHelperCallback( + private val context: Context, + @DrawableRes actionIcon: Int, + private val handler: QuickReplayHandler, + private val clock: Clock, +) : EpoxyTouchHelperCallback() { interface QuickReplayHandler { fun performQuickReplyOnHolder(model: EpoxyModel<*>) @@ -141,7 +145,7 @@ class RoomMessageTouchHelperCallback(private val context: Context, private fun drawReplyButton(canvas: Canvas, itemView: View) { // Timber.v("drawReplyButton") val translationX = abs(itemView.translationX) - val newTime = System.currentTimeMillis() + val newTime = clock.epochMillis() val dt = min(17, newTime - lastReplyButtonAnimationTime) lastReplyButtonAnimationTime = newTime val showing = translationX >= minShowDistance diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/StartCallActionsHandler.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/StartCallActionsHandler.kt index 193dc42f33..d75b9ff69d 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/StartCallActionsHandler.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/StartCallActionsHandler.kt @@ -63,12 +63,14 @@ class StartCallActionsHandler( if (currentCall?.signalingRoomId == roomId) { onTapToReturnToCall() } else if (!state.isAllowedToStartWebRTCCall) { - showDialogWithMessage(fragment.getString( - if (state.isDm()) { - R.string.no_permissions_to_start_webrtc_call_in_direct_room - } else { - R.string.no_permissions_to_start_webrtc_call - }) + showDialogWithMessage( + fragment.getString( + if (state.isDm()) { + R.string.no_permissions_to_start_webrtc_call_in_direct_room + } else { + R.string.no_permissions_to_start_webrtc_call + } + ) ) } else { safeStartCall(isVideoCall) @@ -79,13 +81,15 @@ class StartCallActionsHandler( // can you add widgets?? if (!state.isAllowedToManageWidgets) { // You do not have permission to start a conference call in this room - showDialogWithMessage(fragment.getString( - if (state.isDm()) { - R.string.no_permissions_to_start_conf_call_in_direct_room - } else { - R.string.no_permissions_to_start_conf_call - } - )) + showDialogWithMessage( + fragment.getString( + if (state.isDm()) { + R.string.no_permissions_to_start_conf_call_in_direct_room + } else { + R.string.no_permissions_to_start_conf_call + } + ) + ) } else { if (state.hasActiveJitsiWidget()) { // A conference is already in progress, return @@ -123,18 +127,22 @@ class StartCallActionsHandler( val startCallAction = RoomDetailAction.StartCall(isVideoCall) timelineViewModel.pendingAction = startCallAction if (isVideoCall) { - if (checkPermissions(PERMISSIONS_FOR_VIDEO_IP_CALL, + if (checkPermissions( + PERMISSIONS_FOR_VIDEO_IP_CALL, fragment.requireActivity(), startCallActivityResultLauncher, - R.string.permissions_rationale_msg_camera_and_audio)) { + R.string.permissions_rationale_msg_camera_and_audio + )) { timelineViewModel.pendingAction = null timelineViewModel.handle(startCallAction) } } else { - if (checkPermissions(PERMISSIONS_FOR_AUDIO_IP_CALL, + if (checkPermissions( + PERMISSIONS_FOR_AUDIO_IP_CALL, fragment.requireActivity(), startCallActivityResultLauncher, - R.string.permissions_rationale_msg_record_audio)) { + R.string.permissions_rationale_msg_record_audio + )) { timelineViewModel.pendingAction = null timelineViewModel.handle(startCallAction) } diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/TimelineFragment.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/TimelineFragment.kt index 32f0e46eac..635b00c05d 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/TimelineFragment.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/TimelineFragment.kt @@ -105,6 +105,7 @@ import im.vector.app.core.utils.colorizeMatchingText import im.vector.app.core.utils.copyToClipboard import im.vector.app.core.utils.createJSonViewerStyleProvider import im.vector.app.core.utils.createUIHandler +import im.vector.app.core.utils.isAnimationEnabled import im.vector.app.core.utils.isValidUrl import im.vector.app.core.utils.onPermissionDeniedDialog import im.vector.app.core.utils.onPermissionDeniedSnackbar @@ -119,8 +120,6 @@ import im.vector.app.core.utils.startInstallFromSourceIntent import im.vector.app.core.utils.toast import im.vector.app.databinding.DialogReportContentBinding import im.vector.app.databinding.FragmentTimelineBinding -import im.vector.app.features.MainActivity -import im.vector.app.features.MainActivityArgs import im.vector.app.features.analytics.extensions.toAnalyticsInteraction import im.vector.app.features.analytics.plan.Interaction import im.vector.app.features.analytics.plan.MobileScreen @@ -298,7 +297,7 @@ class TimelineFragment @Inject constructor( private const val ircPattern = " (IRC)" } - private val galleryOrCameraDialogHelper = GalleryOrCameraDialogHelper(this, colorProvider) + private val galleryOrCameraDialogHelper = GalleryOrCameraDialogHelper(this, colorProvider, clock) private val timelineArgs: TimelineArgs by args() private val glideRequests by lazy { @@ -588,6 +587,10 @@ class TimelineFragment @Inject constructor( } private fun handleChatEffect(chatEffect: ChatEffect) { + if (!requireContext().isAnimationEnabled()) { + Timber.d("Do not perform chat effect, animations are disabled.") + return + } when (chatEffect) { ChatEffect.CONFETTI -> { views.viewKonfetti.isVisible = true @@ -668,11 +671,13 @@ class TimelineFragment @Inject constructor( ).apply { directListener = { granted -> if (granted) { - timelineViewModel.handle(RoomDetailAction.EnsureNativeWidgetAllowed( - widget = it.widget, - userJustAccepted = true, - grantedEvents = it.grantedEvents - )) + timelineViewModel.handle( + RoomDetailAction.EnsureNativeWidgetAllowed( + widget = it.widget, + userJustAccepted = true, + grantedEvents = it.grantedEvents + ) + ) } } } @@ -721,8 +726,8 @@ class TimelineFragment @Inject constructor( } /** - * Ensure dismiss actions only trigger when the fragment is in the started state - * EmojiPopup by default dismisses onViewDetachedFromWindow, this can cause race conditions with onDestroyView + * Ensure dismiss actions only trigger when the fragment is in the started state. + * EmojiPopup by default dismisses onViewDetachedFromWindow, this can cause race conditions with onDestroyView. */ private fun EmojiPopup.Builder.setOnEmojiPopupDismissListenerLifecycleAware(action: () -> Unit): EmojiPopup.Builder { return setOnEmojiPopupDismissListener { @@ -793,25 +798,29 @@ class TimelineFragment @Inject constructor( override fun onSendVoiceMessage() { messageComposerViewModel.handle( - MessageComposerAction.EndRecordingVoiceMessage(isCancelled = false, rootThreadEventId = getRootThreadEventId())) + MessageComposerAction.EndRecordingVoiceMessage(isCancelled = false, rootThreadEventId = getRootThreadEventId()) + ) updateRecordingUiState(RecordingUiState.Idle) } override fun onDeleteVoiceMessage() { messageComposerViewModel.handle( - MessageComposerAction.EndRecordingVoiceMessage(isCancelled = true, rootThreadEventId = getRootThreadEventId())) + MessageComposerAction.EndRecordingVoiceMessage(isCancelled = true, rootThreadEventId = getRootThreadEventId()) + ) updateRecordingUiState(RecordingUiState.Idle) } override fun onRecordingLimitReached() { messageComposerViewModel.handle( - MessageComposerAction.PauseRecordingVoiceMessage) + MessageComposerAction.PauseRecordingVoiceMessage + ) updateRecordingUiState(RecordingUiState.Draft) } override fun onRecordingWaveformClicked() { messageComposerViewModel.handle( - MessageComposerAction.PauseRecordingVoiceMessage) + MessageComposerAction.PauseRecordingVoiceMessage + ) updateRecordingUiState(RecordingUiState.Draft) } @@ -829,7 +838,8 @@ class TimelineFragment @Inject constructor( private fun updateRecordingUiState(state: RecordingUiState) { messageComposerViewModel.handle( - MessageComposerAction.OnVoiceRecordingUiStateChanged(state)) + MessageComposerAction.OnVoiceRecordingUiStateChanged(state) + ) } } } @@ -1157,7 +1167,7 @@ class TimelineFragment @Inject constructor( } /** - * Update menu thread notification badge appropriately + * Update menu thread notification badge appropriately. */ private fun updateMenuThreadNotificationBadge(menu: Menu, state: RoomDetailViewState) { val menuThreadList = menu.findItem(R.id.menu_timeline_thread_list).actionView @@ -1180,7 +1190,7 @@ class TimelineFragment @Inject constructor( } /** - * View and highlight the original root thread message in the main timeline + * View and highlight the original root thread message in the main timeline. */ private fun handleViewInRoomAction() { getRootThreadEventId()?.let { @@ -1445,7 +1455,7 @@ class TimelineFragment @Inject constructor( } } } - val swipeCallback = RoomMessageTouchHelperCallback(requireContext(), R.drawable.ic_reply, quickReplyHandler) + val swipeCallback = RoomMessageTouchHelperCallback(requireContext(), R.drawable.ic_reply, quickReplyHandler, clock) val touchHelper = ItemTouchHelper(swipeCallback) touchHelper.attachToRecyclerView(views.timelineRecyclerView) } @@ -1519,7 +1529,7 @@ class TimelineFragment @Inject constructor( views.composerLayout.views.composerEmojiButton.isVisible = vectorPreferences.showEmojiKeyboard() - if (isThreadTimeLine() && timelineArgs.threadTimelineArgs?.startsThread == true) { + if (isThreadTimeLine() && timelineArgs.threadTimelineArgs?.showKeyboard == true) { // Show keyboard when the user started a thread views.composerLayout.views.composerEditText.showKeyboard(andRequestFocus = true) } @@ -1529,9 +1539,11 @@ class TimelineFragment @Inject constructor( attachmentTypeSelector = AttachmentTypeSelectorView(vectorBaseActivity, vectorBaseActivity.layoutInflater, this@TimelineFragment) attachmentTypeSelector.setAttachmentVisibility( AttachmentTypeSelectorView.Type.LOCATION, - vectorPreferences.isLocationSharingEnabled()) + vectorPreferences.isLocationSharingEnabled() + ) attachmentTypeSelector.setAttachmentVisibility( - AttachmentTypeSelectorView.Type.POLL, !isThreadTimeLine()) + AttachmentTypeSelectorView.Type.POLL, !isThreadTimeLine() + ) } attachmentTypeSelector.show(views.composerLayout.views.attachmentButton) } @@ -1730,14 +1742,10 @@ class TimelineFragment @Inject constructor( dismissLoadingDialog() views.composerLayout.setTextIfDifferent("") when (parsedCommand) { - is ParsedCommand.SetMarkdown -> { + is ParsedCommand.SetMarkdown -> { showSnackWithMessage(getString(if (parsedCommand.enable) R.string.markdown_has_been_enabled else R.string.markdown_has_been_disabled)) } - is ParsedCommand.UnignoreUser -> { - // A user has been un-ignored, perform a initial sync - MainActivity.restartApp(requireActivity(), MainActivityArgs(clearCache = true)) - } - else -> Unit + else -> Unit } } @@ -2192,7 +2200,8 @@ class TimelineFragment @Inject constructor( file = it, title = action.messageContent.body, mediaMimeType = action.messageContent.mimeType ?: getMimeTypeFromUri(requireContext(), it.toUri()), - notificationUtils = notificationUtils + notificationUtils = notificationUtils, + currentTimeMillis = clock.epochMillis() ) } .onFailure { @@ -2297,12 +2306,18 @@ class TimelineFragment @Inject constructor( handleCancelSend(action) } is EventSharedAction.ReportContentSpam -> { - timelineViewModel.handle(RoomDetailAction.ReportContent( - action.eventId, action.senderId, "This message is spam", spam = true)) + timelineViewModel.handle( + RoomDetailAction.ReportContent( + action.eventId, action.senderId, "This message is spam", spam = true + ) + ) } is EventSharedAction.ReportContentInappropriate -> { - timelineViewModel.handle(RoomDetailAction.ReportContent( - action.eventId, action.senderId, "This message is inappropriate", inappropriate = true)) + timelineViewModel.handle( + RoomDetailAction.ReportContent( + action.eventId, action.senderId, "This message is inappropriate", inappropriate = true + ) + ) } is EventSharedAction.ReportContentCustom -> { promptReasonToReportContent(action) @@ -2428,7 +2443,11 @@ class TimelineFragment @Inject constructor( private fun onReplyInThreadClicked(action: EventSharedAction.ReplyInThread) { if (vectorPreferences.areThreadMessagesEnabled()) { - navigateToThreadTimeline(action.eventId, action.startsThread) + navigateToThreadTimeline( + rootThreadEventId = action.eventId, + startsThread = action.startsThread, + showKeyboard = true + ) } else { displayThreadsBetaOptInDialog() } @@ -2436,10 +2455,9 @@ class TimelineFragment @Inject constructor( /** * Navigate to Threads timeline for the specified rootThreadEventId - * using the ThreadsActivity + * using the ThreadsActivity. */ - - private fun navigateToThreadTimeline(rootThreadEventId: String, startsThread: Boolean = false) { + private fun navigateToThreadTimeline(rootThreadEventId: String, startsThread: Boolean = false, showKeyboard: Boolean = false) { analyticsTracker.capture(Interaction.Name.MobileRoomThreadSummaryItem.toAnalyticsInteraction()) context?.let { val roomThreadDetailArgs = ThreadTimelineArgs( @@ -2448,7 +2466,9 @@ class TimelineFragment @Inject constructor( displayName = timelineViewModel.getRoomSummary()?.displayName, avatarUrl = timelineViewModel.getRoomSummary()?.avatarUrl, roomEncryptionTrustLevel = timelineViewModel.getRoomSummary()?.roomEncryptionTrustLevel, - rootThreadEventId = rootThreadEventId) + rootThreadEventId = rootThreadEventId, + showKeyboard = showKeyboard + ) navigator.openThread(it, roomThreadDetailArgs) } } @@ -2474,9 +2494,8 @@ class TimelineFragment @Inject constructor( /** * Navigate to Threads list for the current room - * using the ThreadsActivity + * using the ThreadsActivity. */ - private fun navigateToThreadList() { analyticsTracker.capture(Interaction.Name.MobileRoomThreadListButton.toAnalyticsInteraction()) context?.let { @@ -2484,7 +2503,8 @@ class TimelineFragment @Inject constructor( roomId = timelineArgs.roomId, displayName = timelineViewModel.getRoomSummary()?.displayName, roomEncryptionTrustLevel = timelineViewModel.getRoomSummary()?.roomEncryptionTrustLevel, - avatarUrl = timelineViewModel.getRoomSummary()?.avatarUrl) + avatarUrl = timelineViewModel.getRoomSummary()?.avatarUrl + ) navigator.openThreadList(it, roomThreadDetailArgs) } } @@ -2602,12 +2622,12 @@ class TimelineFragment @Inject constructor( } /** - * Returns true if the current room is a Thread room, false otherwise + * Returns true if the current room is a Thread room, false otherwise. */ private fun isThreadTimeLine(): Boolean = timelineArgs.threadTimelineArgs?.rootThreadEventId != null /** - * Returns the root thread event if we are in a thread room, otherwise returns null + * Returns the root thread event if we are in a thread room, otherwise returns null. */ fun getRootThreadEventId(): String? = timelineArgs.threadTimelineArgs?.rootThreadEventId } diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/TimelineViewModel.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/TimelineViewModel.kt index b79b55b248..e81bab3e3e 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/TimelineViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/TimelineViewModel.kt @@ -40,6 +40,7 @@ import im.vector.app.core.utils.BehaviorDataSource import im.vector.app.features.analytics.AnalyticsTracker import im.vector.app.features.analytics.DecryptionFailureTracker import im.vector.app.features.analytics.extensions.toAnalyticsJoinedRoom +import im.vector.app.features.analytics.plan.JoinedRoom import im.vector.app.features.call.conference.ConferenceEvent import im.vector.app.features.call.conference.JitsiActiveConferenceHolder import im.vector.app.features.call.conference.JitsiService @@ -86,7 +87,10 @@ import org.matrix.android.sdk.api.session.events.model.isTextMessage import org.matrix.android.sdk.api.session.events.model.toContent import org.matrix.android.sdk.api.session.events.model.toModel import org.matrix.android.sdk.api.session.file.FileService +import org.matrix.android.sdk.api.session.getRoom import org.matrix.android.sdk.api.session.initsync.SyncStatusService +import org.matrix.android.sdk.api.session.room.getStateEvent +import org.matrix.android.sdk.api.session.room.getTimelineEvent import org.matrix.android.sdk.api.session.room.members.ChangeMembershipState import org.matrix.android.sdk.api.session.room.members.roomMemberQueryParams import org.matrix.android.sdk.api.session.room.model.Membership @@ -181,18 +185,18 @@ class TimelineViewModel @AssistedInject constructor( setupPreviewUrlObservers() room.getRoomSummaryLive() viewModelScope.launch(Dispatchers.IO) { - tryOrNull { room.markAsRead(ReadService.MarkAsReadParams.READ_RECEIPT) } + tryOrNull { room.readService().markAsRead(ReadService.MarkAsReadParams.READ_RECEIPT) } } // Inform the SDK that the room is displayed viewModelScope.launch(Dispatchers.IO) { - tryOrNull { session.onRoomDisplayed(initialState.roomId) } + tryOrNull { session.roomService().onRoomDisplayed(initialState.roomId) } } callManager.addProtocolsCheckerListener(this) callManager.checkForProtocolsSupportIfNeeded() chatEffectManager.delegate = this // Ensure to share the outbound session keys with all members - if (OutboundSessionKeySharingStrategy.WhenEnteringRoom == BuildConfig.outboundSessionKeySharingStrategy && room.isEncrypted()) { + if (OutboundSessionKeySharingStrategy.WhenEnteringRoom == BuildConfig.outboundSessionKeySharingStrategy && room.roomCryptoService().isEncrypted()) { prepareForEncryption() } @@ -214,7 +218,8 @@ class TimelineViewModel @AssistedInject constructor( .flattenParentIds.firstOrNull { it.isNotBlank() }, // force persist, because if not on resume the AppStateHandler will resume // the current space from what was persisted on enter background - persistNow = true) + persistNow = true + ) } } } @@ -227,7 +232,7 @@ class TimelineViewModel @AssistedInject constructor( } /** - * Threads specific initialization + * Threads specific initialization. */ private fun initThreads() { markThreadTimelineAsReadLocal() @@ -248,7 +253,7 @@ class TimelineViewModel @AssistedInject constructor( prepareToEncrypt = Loading() viewModelScope.launch { runCatching { - room.prepareToEncrypt() + room.roomCryptoService().prepareToEncrypt() }.fold({ prepareToEncrypt = Success(Unit) }, { @@ -346,19 +351,19 @@ class TimelineViewModel @AssistedInject constructor( } /** - * Mark the thread as read, while the user navigated within the thread - * This is a local implementation has nothing to do with APIs + * Mark the thread as read, while the user navigated within the thread. + * This is a local implementation has nothing to do with APIs. */ private fun markThreadTimelineAsReadLocal() { initialState.rootThreadEventId?.let { session.coroutineScope.launch { - room.markThreadAsRead(it) + room.threadsLocalService().markThreadAsRead(it) } } } /** - * Observe local unread threads + * Observe local unread threads. */ private fun observeLocalThreadNotifications() { room.flow() @@ -369,9 +374,12 @@ class TimelineViewModel @AssistedInject constructor( threadRootEvent.root.threadDetails?.threadNotificationState == ThreadNotificationState.NEW_HIGHLIGHTED_MESSAGE }?.let { true } ?: false val numberOfLocalUnreadThreads = threadList?.size ?: 0 - copy(threadNotificationBadgeState = ThreadNotificationBadgeState( - numberOfLocalUnreadThreads = numberOfLocalUnreadThreads, - isUserMentioned = isUserMentioned)) + copy( + threadNotificationBadgeState = ThreadNotificationBadgeState( + numberOfLocalUnreadThreads = numberOfLocalUnreadThreads, + isUserMentioned = isUserMentioned + ) + ) } } @@ -488,7 +496,7 @@ class TimelineViewModel @AssistedInject constructor( private fun handleSetNewAvatar(action: RoomDetailAction.SetAvatarAction) { viewModelScope.launch(Dispatchers.IO) { try { - room.updateAvatar(action.newAvatarUri, action.newAvatarFileName) + room.stateService().updateAvatar(action.newAvatarUri, action.newAvatarFileName) _viewEvents.post(RoomDetailViewEvents.ActionSuccess(action)) } catch (failure: Throwable) { _viewEvents.post(RoomDetailViewEvents.ActionFailure(action, failure)) @@ -505,19 +513,22 @@ class TimelineViewModel @AssistedInject constructor( } private fun handleJumpToReadReceipt(action: RoomDetailAction.JumpToReadReceipt) { - room.getUserReadReceipt(action.userId) + room.readService().getUserReadReceipt(action.userId) ?.let { handleNavigateToEvent(RoomDetailAction.NavigateToEvent(it, true)) } } private fun handleSendSticker(action: RoomDetailAction.SendSticker) { val content = initialState.rootThreadEventId?.let { - action.stickerContent.copy(relatesTo = RelationDefaultContent( - type = RelationType.THREAD, - isFallingBack = true, - eventId = it)) + action.stickerContent.copy( + relatesTo = RelationDefaultContent( + type = RelationType.THREAD, + isFallingBack = true, + eventId = it + ) + ) } ?: action.stickerContent - room.sendEvent(EventType.STICKER, content.toContent()) + room.sendService().sendEvent(EventType.STICKER, content.toContent()) } private fun handleStartCall(action: RoomDetailAction.StartCall) { @@ -637,7 +648,7 @@ class TimelineViewModel @AssistedInject constructor( if (trackUnreadMessages.getAndSet(false)) { mostRecentDisplayedEvent?.root?.eventId?.also { session.coroutineScope.launch { - tryOrNull { room.setReadMarker(it) } + tryOrNull { room.readService().setReadMarker(it) } } } mostRecentDisplayedEvent = null @@ -650,12 +661,12 @@ class TimelineViewModel @AssistedInject constructor( } fun getMember(userId: String): RoomMemberSummary? { - return room.getRoomMember(userId) + return room.membershipService().getRoomMember(userId) } private fun handleComposerFocusChange(action: RoomDetailAction.ComposerFocusChange) { // Ensure outbound session keys - if (OutboundSessionKeySharingStrategy.WhenTyping == BuildConfig.outboundSessionKeySharingStrategy && room.isEncrypted()) { + if (OutboundSessionKeySharingStrategy.WhenTyping == BuildConfig.outboundSessionKeySharingStrategy && room.roomCryptoService().isEncrypted()) { if (action.focused) { // Should we add some rate limit here, or do it only once per model lifecycle? prepareForEncryption() @@ -682,7 +693,7 @@ class TimelineViewModel @AssistedInject constructor( } viewModelScope.launch { val result = runCatchingToAsync { - session.joinRoom(roomId, viaServers = viaServers) + session.roomService().joinRoom(roomId, viaServers = viaServers) roomId } setState { @@ -736,36 +747,36 @@ class TimelineViewModel @AssistedInject constructor( // PRIVATE METHODS ***************************************************************************** private fun handleSendReaction(action: RoomDetailAction.SendReaction) { - room.sendReaction(action.targetEventId, action.reaction) + room.relationService().sendReaction(action.targetEventId, action.reaction) } private fun handleRedactEvent(action: RoomDetailAction.RedactAction) { val event = room.getTimelineEvent(action.targetEventId) ?: return - room.redactEvent(event.root, action.reason) + room.sendService().redactEvent(event.root, action.reason) } private fun handleUndoReact(action: RoomDetailAction.UndoReaction) { viewModelScope.launch { tryOrNull { - room.undoReaction(action.targetEventId, action.reaction) + room.relationService().undoReaction(action.targetEventId, action.reaction) } } } private fun handleUpdateQuickReaction(action: RoomDetailAction.UpdateQuickReactAction) { if (action.add) { - room.sendReaction(action.targetEventId, action.selectedReaction) + room.relationService().sendReaction(action.targetEventId, action.selectedReaction) } else { viewModelScope.launch { tryOrNull { - room.undoReaction(action.targetEventId, action.selectedReaction) + room.relationService().undoReaction(action.targetEventId, action.selectedReaction) } } } } private fun handleSendMedia(action: RoomDetailAction.SendMedia) { - room.sendMedias( + room.sendService().sendMedias( action.attachments, action.compressBeforeSending, emptySet(), @@ -808,7 +819,7 @@ class TimelineViewModel @AssistedInject constructor( notificationDrawerManager.updateEvents { it.clearMemberShipNotificationForRoom(initialState.roomId) } viewModelScope.launch { try { - session.leaveRoom(room.roomId) + session.roomService().leaveRoom(room.roomId) } catch (throwable: Throwable) { _viewEvents.post(RoomDetailViewEvents.Failure(throwable, showInDialog = true)) } @@ -819,24 +830,35 @@ class TimelineViewModel @AssistedInject constructor( notificationDrawerManager.updateEvents { it.clearMemberShipNotificationForRoom(initialState.roomId) } viewModelScope.launch { try { - session.joinRoom(room.roomId) - analyticsTracker.capture(room.roomSummary().toAnalyticsJoinedRoom()) + session.roomService().joinRoom(room.roomId) + trackRoomJoined() } catch (throwable: Throwable) { _viewEvents.post(RoomDetailViewEvents.Failure(throwable, showInDialog = true)) } } } + private fun trackRoomJoined() { + val trigger = if (initialState.isInviteAlreadyAccepted) { + JoinedRoom.Trigger.Invite + } else { + JoinedRoom.Trigger.Timeline + } + analyticsTracker.capture(room.roomSummary().toAnalyticsJoinedRoom(trigger)) + } + private fun handleOpenOrDownloadFile(action: RoomDetailAction.DownloadOrOpen) { val mxcUrl = action.messageFileContent.getFileUrl() ?: return val isLocalSendingFile = action.senderId == session.myUserId && mxcUrl.startsWith("content://") if (isLocalSendingFile) { tryOrNull { Uri.parse(mxcUrl) }?.let { - _viewEvents.post(RoomDetailViewEvents.OpenFile( - it, - action.messageFileContent.mimeType - )) + _viewEvents.post( + RoomDetailViewEvents.OpenFile( + it, + action.messageFileContent.mimeType + ) + ) } } else { viewModelScope.launch { @@ -848,21 +870,25 @@ class TimelineViewModel @AssistedInject constructor( session.fileService().downloadFile(messageContent = action.messageFileContent) } - _viewEvents.post(RoomDetailViewEvents.DownloadFileState( - action.messageFileContent.mimeType, - result.getOrNull(), - result.exceptionOrNull() - )) + _viewEvents.post( + RoomDetailViewEvents.DownloadFileState( + action.messageFileContent.mimeType, + result.getOrNull(), + result.exceptionOrNull() + ) + ) canOpen = result.isSuccess } if (canOpen) { // We can now open the file session.fileService().getTemporarySharableURI(action.messageFileContent)?.let { uri -> - _viewEvents.post(RoomDetailViewEvents.OpenFile( - uri, - action.messageFileContent.mimeType - )) + _viewEvents.post( + RoomDetailViewEvents.OpenFile( + uri, + action.messageFileContent.mimeType + ) + ) } } } @@ -891,8 +917,8 @@ class TimelineViewModel @AssistedInject constructor( return } when { - it.root.isTextMessage() -> room.resendTextMessage(it) - it.root.isAttachmentMessage() -> room.resendMediaMessage(it) + it.root.isTextMessage() -> room.sendService().resendTextMessage(it) + it.root.isAttachmentMessage() -> room.sendService().resendMediaMessage(it) else -> { // TODO } @@ -908,13 +934,13 @@ class TimelineViewModel @AssistedInject constructor( Timber.e("Cannot resend message, it is not failed, Cancel first") return } - room.deleteFailedEcho(it) + room.sendService().deleteFailedEcho(it) } } private fun handleCancel(action: RoomDetailAction.CancelSend) { if (action.force) { - room.cancelSend(action.eventId) + room.sendService().cancelSend(action.eventId) return } val targetEventId = action.eventId @@ -924,16 +950,16 @@ class TimelineViewModel @AssistedInject constructor( Timber.e("Cannot cancel message, it is not sending") return } - room.cancelSend(targetEventId) + room.sendService().cancelSend(targetEventId) } } private fun handleResendAll() { - room.resendAllFailedMessages() + room.sendService().resendAllFailedMessages() } private fun handleRemoveAllFailedMessages() { - room.cancelAllFailedMessages() + room.sendService().cancelAllFailedMessages() } private fun observeEventDisplayedActions() { @@ -956,7 +982,7 @@ class TimelineViewModel @AssistedInject constructor( } bufferedMostRecentDisplayedEvent.root.eventId?.let { eventId -> session.coroutineScope.launch { - tryOrNull { room.setReadReceipt(eventId) } + tryOrNull { room.readService().setReadReceipt(eventId) } } } } @@ -973,14 +999,14 @@ class TimelineViewModel @AssistedInject constructor( private fun handleMarkAllAsRead() { setState { copy(unreadState = UnreadState.HasNoUnread) } viewModelScope.launch { - tryOrNull { room.markAsRead(ReadService.MarkAsReadParams.BOTH) } + tryOrNull { room.readService().markAsRead(ReadService.MarkAsReadParams.BOTH) } } } private fun handleReportContent(action: RoomDetailAction.ReportContent) { viewModelScope.launch { val event = try { - room.reportContent(action.eventId, -100, action.reason) + room.reportingService().reportContent(action.eventId, -100, action.reason) RoomDetailViewEvents.ActionSuccess(action) } catch (failure: Exception) { RoomDetailViewEvents.ActionFailure(action, failure) @@ -996,7 +1022,7 @@ class TimelineViewModel @AssistedInject constructor( viewModelScope.launch { val event = try { - session.ignoreUserIds(listOf(action.userId)) + session.userService().ignoreUserIds(listOf(action.userId)) RoomDetailViewEvents.ActionSuccess(action) } catch (failure: Throwable) { RoomDetailViewEvents.ActionFailure(action, failure) @@ -1011,7 +1037,8 @@ class TimelineViewModel @AssistedInject constructor( supportedVerificationMethodsProvider.provide(), action.otherUserId, room.roomId, - action.transactionId)) { + action.transactionId + )) { _viewEvents.post(RoomDetailViewEvents.ActionSuccess(action)) } else { // TODO @@ -1022,7 +1049,8 @@ class TimelineViewModel @AssistedInject constructor( session.cryptoService().verificationService().declineVerificationRequestInDMs( action.otherUserId, action.transactionId, - room.roomId) + room.roomId + ) } private fun handleRequestVerification(action: RoomDetailAction.RequestVerification) { @@ -1035,9 +1063,13 @@ class TimelineViewModel @AssistedInject constructor( session.cryptoService().verificationService().getExistingVerificationRequestInRoom(room.roomId, action.transactionId)?.let { if (it.handledByOtherSession) return if (!it.isFinished) { - _viewEvents.post(RoomDetailViewEvents.ActionSuccess(action.copy( - otherUserId = it.otherUserId - ))) + _viewEvents.post( + RoomDetailViewEvents.ActionSuccess( + action.copy( + otherUserId = it.otherUserId + ) + ) + ) } } } @@ -1070,13 +1102,13 @@ class TimelineViewModel @AssistedInject constructor( room.getTimelineEvent(action.eventId)?.let { pollTimelineEvent -> val currentVote = pollTimelineEvent.annotations?.pollResponseSummary?.aggregatedContent?.myVote if (currentVote != action.optionKey) { - room.voteToPoll(action.eventId, action.optionKey) + room.sendService().voteToPoll(action.eventId, action.optionKey) } } } private fun handleEndPoll(eventId: String) { - room.endPoll(eventId) + room.sendService().endPoll(eventId) } private fun observeSyncState() { @@ -1086,7 +1118,7 @@ class TimelineViewModel @AssistedInject constructor( copy(syncState = syncState) } - session.getSyncStatusLive() + session.syncStatusService().getSyncStatusLive() .asFlow() .filterIsInstance() .setOnEach { @@ -1190,7 +1222,7 @@ class TimelineViewModel @AssistedInject constructor( } if (summary.membership == Membership.INVITE) { summary.inviterId?.let { inviterId -> - session.getRoomMember(inviterId, summary.roomId) + session.roomService().getRoomMember(inviterId, summary.roomId) }?.also { setState { copy(asyncInviter = Success(it)) } } @@ -1254,7 +1286,7 @@ class TimelineViewModel @AssistedInject constructor( timeline.removeAllListeners() decryptionFailureTracker.onTimeLineDisposed(room.roomId) if (vectorPreferences.sendTypingNotifs()) { - room.userStopsTyping() + room.typingService().userStopsTyping() } chatEffectManager.delegate = null chatEffectManager.dispose() diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/composer/AudioMessageHelper.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/composer/AudioMessageHelper.kt index f4cab3305d..98694d9c9e 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/composer/AudioMessageHelper.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/composer/AudioMessageHelper.kt @@ -110,7 +110,7 @@ class AudioMessageHelper @Inject constructor( } /** - * When entering in playback mode actually + * When entering in playback mode actually. */ fun pauseRecording() { voiceRecorder.stopRecord() diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/composer/MessageComposerViewModel.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/composer/MessageComposerViewModel.kt index 9c81a39941..eca5c846ca 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/composer/MessageComposerViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/composer/MessageComposerViewModel.kt @@ -28,6 +28,7 @@ import im.vector.app.core.resources.StringProvider import im.vector.app.features.analytics.AnalyticsTracker import im.vector.app.features.analytics.extensions.toAnalyticsComposer import im.vector.app.features.analytics.extensions.toAnalyticsJoinedRoom +import im.vector.app.features.analytics.plan.JoinedRoom import im.vector.app.features.attachments.toContentAttachmentData import im.vector.app.features.command.CommandParser import im.vector.app.features.command.ParsedCommand @@ -50,6 +51,10 @@ import org.matrix.android.sdk.api.session.events.model.getRootThreadEventId import org.matrix.android.sdk.api.session.events.model.isThread import org.matrix.android.sdk.api.session.events.model.toContent import org.matrix.android.sdk.api.session.events.model.toModel +import org.matrix.android.sdk.api.session.getRoom +import org.matrix.android.sdk.api.session.getRoomSummary +import org.matrix.android.sdk.api.session.room.getStateEvent +import org.matrix.android.sdk.api.session.room.getTimelineEvent import org.matrix.android.sdk.api.session.room.model.PowerLevelsContent import org.matrix.android.sdk.api.session.room.model.RoomAvatarContent import org.matrix.android.sdk.api.session.room.model.RoomEncryptionAlgorithm @@ -198,16 +203,18 @@ class MessageComposerViewModel @AssistedInject constructor( is SendMode.Regular -> { when (val parsedCommand = commandParser.parseSlashCommand( textMessage = action.text, - isInThreadTimeline = state.isInThreadTimeline())) { + isInThreadTimeline = state.isInThreadTimeline() + )) { is ParsedCommand.ErrorNotACommand -> { // Send the text message to the room if (state.rootThreadEventId != null) { - room.replyInThread( + room.relationService().replyInThread( rootThreadEventId = state.rootThreadEventId, replyInThreadText = action.text, - autoMarkdown = action.autoMarkdown) + autoMarkdown = action.autoMarkdown + ) } else { - room.sendTextMessage(action.text, autoMarkdown = action.autoMarkdown) + room.sendService().sendTextMessage(action.text, autoMarkdown = action.autoMarkdown) } _viewEvents.post(MessageComposerViewEvents.MessageSent) @@ -228,12 +235,13 @@ class MessageComposerViewModel @AssistedInject constructor( is ParsedCommand.SendPlainText -> { // Send the text message to the room, without markdown if (state.rootThreadEventId != null) { - room.replyInThread( + room.relationService().replyInThread( rootThreadEventId = state.rootThreadEventId, replyInThreadText = parsedCommand.message, - autoMarkdown = false) + autoMarkdown = false + ) } else { - room.sendTextMessage(parsedCommand.message, autoMarkdown = false) + room.sendService().sendTextMessage(parsedCommand.message, autoMarkdown = false) } _viewEvents.post(MessageComposerViewEvents.MessageSent) popDraft() @@ -283,13 +291,18 @@ class MessageComposerViewModel @AssistedInject constructor( } is ParsedCommand.SendEmote -> { if (state.rootThreadEventId != null) { - room.replyInThread( + room.relationService().replyInThread( rootThreadEventId = state.rootThreadEventId, replyInThreadText = parsedCommand.message, msgType = MessageType.MSGTYPE_EMOTE, - autoMarkdown = action.autoMarkdown) + autoMarkdown = action.autoMarkdown + ) } else { - room.sendTextMessage(parsedCommand.message, msgType = MessageType.MSGTYPE_EMOTE, autoMarkdown = action.autoMarkdown) + room.sendService().sendTextMessage( + text = parsedCommand.message, + msgType = MessageType.MSGTYPE_EMOTE, + autoMarkdown = action.autoMarkdown + ) } _viewEvents.post(MessageComposerViewEvents.SlashCommandResultOk(parsedCommand)) popDraft() @@ -297,12 +310,13 @@ class MessageComposerViewModel @AssistedInject constructor( is ParsedCommand.SendRainbow -> { val message = parsedCommand.message.toString() if (state.rootThreadEventId != null) { - room.replyInThread( + room.relationService().replyInThread( rootThreadEventId = state.rootThreadEventId, replyInThreadText = parsedCommand.message, - formattedText = rainbowGenerator.generate(message)) + formattedText = rainbowGenerator.generate(message) + ) } else { - room.sendFormattedTextMessage(message, rainbowGenerator.generate(message)) + room.sendService().sendFormattedTextMessage(message, rainbowGenerator.generate(message)) } _viewEvents.post(MessageComposerViewEvents.SlashCommandResultOk(parsedCommand)) popDraft() @@ -310,13 +324,14 @@ class MessageComposerViewModel @AssistedInject constructor( is ParsedCommand.SendRainbowEmote -> { val message = parsedCommand.message.toString() if (state.rootThreadEventId != null) { - room.replyInThread( + room.relationService().replyInThread( rootThreadEventId = state.rootThreadEventId, replyInThreadText = parsedCommand.message, msgType = MessageType.MSGTYPE_EMOTE, - formattedText = rainbowGenerator.generate(message)) + formattedText = rainbowGenerator.generate(message) + ) } else { - room.sendFormattedTextMessage(message, rainbowGenerator.generate(message), MessageType.MSGTYPE_EMOTE) + room.sendService().sendFormattedTextMessage(message, rainbowGenerator.generate(message), MessageType.MSGTYPE_EMOTE) } _viewEvents.post(MessageComposerViewEvents.SlashCommandResultOk(parsedCommand)) @@ -326,14 +341,16 @@ class MessageComposerViewModel @AssistedInject constructor( val text = "[${stringProvider.getString(R.string.spoiler)}](${parsedCommand.message})" val formattedText = "${parsedCommand.message}" if (state.rootThreadEventId != null) { - room.replyInThread( + room.relationService().replyInThread( rootThreadEventId = state.rootThreadEventId, replyInThreadText = text, - formattedText = formattedText) + formattedText = formattedText + ) } else { - room.sendFormattedTextMessage( + room.sendService().sendFormattedTextMessage( text, - formattedText) + formattedText + ) } _viewEvents.post(MessageComposerViewEvents.SlashCommandResultOk(parsedCommand)) popDraft() @@ -374,7 +391,7 @@ class MessageComposerViewModel @AssistedInject constructor( popDraft() } is ParsedCommand.DiscardSession -> { - if (room.isEncrypted()) { + if (room.roomCryptoService().isEncrypted()) { session.cryptoService().discardOutboundSession(room.roomId) _viewEvents.post(MessageComposerViewEvents.SlashCommandResultOk(parsedCommand)) popDraft() @@ -445,7 +462,7 @@ class MessageComposerViewModel @AssistedInject constructor( is ParsedCommand.LeaveRoom -> { viewModelScope.launch(Dispatchers.IO) { try { - session.leaveRoom(parsedCommand.roomId) + session.roomService().leaveRoom(parsedCommand.roomId) popDraft() _viewEvents.post(MessageComposerViewEvents.SlashCommandResultOk(parsedCommand)) } catch (failure: Throwable) { @@ -486,16 +503,18 @@ class MessageComposerViewModel @AssistedInject constructor( if (inReplyTo != null) { // TODO check if same content? room.getTimelineEvent(inReplyTo)?.let { - room.editReply(state.sendMode.timelineEvent, it, action.text.toString()) + room.relationService().editReply(state.sendMode.timelineEvent, it, action.text.toString()) } } else { val messageContent = state.sendMode.timelineEvent.getLastMessageContent() val existingBody = messageContent?.body ?: "" if (existingBody != action.text) { - room.editTextMessage(state.sendMode.timelineEvent, + room.relationService().editTextMessage( + state.sendMode.timelineEvent, messageContent?.msgType ?: MessageType.MSGTYPE_TEXT, action.text, - action.autoMarkdown) + action.autoMarkdown + ) } else { Timber.w("Same message content, do not send edition") } @@ -504,11 +523,12 @@ class MessageComposerViewModel @AssistedInject constructor( popDraft() } is SendMode.Quote -> { - room.sendQuotedTextMessage( + room.sendService().sendQuotedTextMessage( quotedEvent = state.sendMode.timelineEvent, text = action.text.toString(), autoMarkdown = action.autoMarkdown, - rootThreadEventId = state.rootThreadEventId) + rootThreadEventId = state.rootThreadEventId + ) _viewEvents.post(MessageComposerViewEvents.MessageSent) popDraft() } @@ -518,12 +538,13 @@ class MessageComposerViewModel @AssistedInject constructor( // If threads are disabled this will make the fallback replies visible to clients with threads enabled val rootThreadEventId = if (showInThread) timelineEvent.root.getRootThreadEventId() else null state.rootThreadEventId?.let { - room.replyInThread( + room.relationService().replyInThread( rootThreadEventId = it, replyInThreadText = action.text.toString(), autoMarkdown = action.autoMarkdown, - eventReplied = timelineEvent) - } ?: room.replyToMessage( + eventReplied = timelineEvent + ) + } ?: room.relationService().replyToMessage( eventReplied = timelineEvent, replyText = action.text.toString(), autoMarkdown = action.autoMarkdown, @@ -549,13 +570,13 @@ class MessageComposerViewModel @AssistedInject constructor( // Otherwise we clear the composer and remove the draft from db setState { copy(sendMode = SendMode.Regular("", false)) } viewModelScope.launch { - room.deleteDraft() + room.draftService().deleteDraft() } } } private fun loadDraftIfAny() { - val currentDraft = room.getDraft() + val currentDraft = room.draftService().getDraft() setState { copy( // Create a sendMode from a draft and retrieve the TimelineEvent @@ -586,9 +607,9 @@ class MessageComposerViewModel @AssistedInject constructor( private fun handleUserIsTyping(action: MessageComposerAction.UserIsTyping) { if (vectorPreferences.sendTypingNotifs()) { if (action.isTyping) { - room.userIsTyping() + room.typingService().userIsTyping() } else { - room.userStopsTyping() + room.typingService().userStopsTyping() } } } @@ -596,26 +617,28 @@ class MessageComposerViewModel @AssistedInject constructor( private fun sendChatEffect(sendChatEffect: ParsedCommand.SendChatEffect) { // If message is blank, convert to an emote, with default message if (sendChatEffect.message.isBlank()) { - val defaultMessage = stringProvider.getString(when (sendChatEffect.chatEffect) { - ChatEffect.CONFETTI -> R.string.default_message_emote_confetti - ChatEffect.SNOWFALL -> R.string.default_message_emote_snow - }) - room.sendTextMessage(defaultMessage, MessageType.MSGTYPE_EMOTE) + val defaultMessage = stringProvider.getString( + when (sendChatEffect.chatEffect) { + ChatEffect.CONFETTI -> R.string.default_message_emote_confetti + ChatEffect.SNOWFALL -> R.string.default_message_emote_snow + } + ) + room.sendService().sendTextMessage(defaultMessage, MessageType.MSGTYPE_EMOTE) } else { - room.sendTextMessage(sendChatEffect.message, sendChatEffect.chatEffect.toMessageType()) + room.sendService().sendTextMessage(sendChatEffect.message, sendChatEffect.chatEffect.toMessageType()) } } private fun handleJoinToAnotherRoomSlashCommand(command: ParsedCommand.JoinRoom) { viewModelScope.launch { try { - session.joinRoom(command.roomAlias, command.reason, emptyList()) + session.roomService().joinRoom(command.roomAlias, command.reason, emptyList()) } catch (failure: Throwable) { _viewEvents.post(MessageComposerViewEvents.SlashCommandResultError(failure)) return@launch } session.getRoomSummary(command.roomAlias) - ?.also { analyticsTracker.capture(it.toAnalyticsJoinedRoom()) } + ?.also { analyticsTracker.capture(it.toAnalyticsJoinedRoom(JoinedRoom.Trigger.SlashCommand)) } ?.roomId ?.let { _viewEvents.post(MessageComposerViewEvents.JoinRoomCommandSuccess(it)) @@ -645,19 +668,19 @@ class MessageComposerViewModel @AssistedInject constructor( private fun handleChangeTopicSlashCommand(changeTopic: ParsedCommand.ChangeTopic) { launchSlashCommandFlowSuspendable(changeTopic) { - room.updateTopic(changeTopic.topic) + room.stateService().updateTopic(changeTopic.topic) } } private fun handleInviteSlashCommand(invite: ParsedCommand.Invite) { launchSlashCommandFlowSuspendable(invite) { - room.invite(invite.userId, invite.reason) + room.membershipService().invite(invite.userId, invite.reason) } } private fun handleInvite3pidSlashCommand(invite: ParsedCommand.Invite3Pid) { launchSlashCommandFlowSuspendable(invite) { - room.invite3pid(invite.threePid) + room.membershipService().invite3pid(invite.threePid) } } @@ -670,13 +693,13 @@ class MessageComposerViewModel @AssistedInject constructor( ?: return launchSlashCommandFlowSuspendable(setUserPowerLevel) { - room.sendStateEvent(EventType.STATE_ROOM_POWER_LEVELS, stateKey = "", newPowerLevelsContent) + room.stateService().sendStateEvent(EventType.STATE_ROOM_POWER_LEVELS, stateKey = "", newPowerLevelsContent) } } private fun handleChangeDisplayNameSlashCommand(changeDisplayName: ParsedCommand.ChangeDisplayName) { launchSlashCommandFlowSuspendable(changeDisplayName) { - session.setDisplayName(session.myUserId, changeDisplayName.displayName) + session.profileService().setDisplayName(session.myUserId, changeDisplayName.displayName) } } @@ -691,32 +714,32 @@ class MessageComposerViewModel @AssistedInject constructor( ?.let { session.getRoom(it) } } ?.let { - session.leaveRoom(it.roomId) + session.roomService().leaveRoom(it.roomId) } } } private fun handleRemoveSlashCommand(removeUser: ParsedCommand.RemoveUser) { launchSlashCommandFlowSuspendable(removeUser) { - room.remove(removeUser.userId, removeUser.reason) + room.membershipService().remove(removeUser.userId, removeUser.reason) } } private fun handleBanSlashCommand(ban: ParsedCommand.BanUser) { launchSlashCommandFlowSuspendable(ban) { - room.ban(ban.userId, ban.reason) + room.membershipService().ban(ban.userId, ban.reason) } } private fun handleUnbanSlashCommand(unban: ParsedCommand.UnbanUser) { launchSlashCommandFlowSuspendable(unban) { - room.unban(unban.userId, unban.reason) + room.membershipService().unban(unban.userId, unban.reason) } } private fun handleChangeRoomNameSlashCommand(changeRoomName: ParsedCommand.ChangeRoomName) { launchSlashCommandFlowSuspendable(changeRoomName) { - room.updateName(changeRoomName.name) + room.stateService().updateName(changeRoomName.name) } } @@ -732,14 +755,14 @@ class MessageComposerViewModel @AssistedInject constructor( ?.copy(displayName = changeDisplayName.displayName) ?.toContent() ?.let { - room.sendStateEvent(EventType.STATE_ROOM_MEMBER, session.myUserId, it) + room.stateService().sendStateEvent(EventType.STATE_ROOM_MEMBER, session.myUserId, it) } } } private fun handleChangeRoomAvatarSlashCommand(changeAvatar: ParsedCommand.ChangeRoomAvatar) { launchSlashCommandFlowSuspendable(changeAvatar) { - room.sendStateEvent(EventType.STATE_ROOM_AVATAR, stateKey = "", RoomAvatarContent(changeAvatar.url).toContent()) + room.stateService().sendStateEvent(EventType.STATE_ROOM_AVATAR, stateKey = "", RoomAvatarContent(changeAvatar.url).toContent()) } } @@ -749,14 +772,14 @@ class MessageComposerViewModel @AssistedInject constructor( ?.copy(avatarUrl = changeAvatar.url) ?.toContent() ?.let { - room.sendStateEvent(EventType.STATE_ROOM_MEMBER, session.myUserId, it) + room.stateService().sendStateEvent(EventType.STATE_ROOM_MEMBER, session.myUserId, it) } } } private fun handleIgnoreSlashCommand(ignore: ParsedCommand.IgnoreUser) { launchSlashCommandFlowSuspendable(ignore) { - session.ignoreUserIds(listOf(ignore.userId)) + session.userService().ignoreUserIds(listOf(ignore.userId)) } } @@ -773,7 +796,7 @@ class MessageComposerViewModel @AssistedInject constructor( private fun handleUnignoreSlashCommandConfirmed(unignore: ParsedCommand.UnignoreUser) { launchSlashCommandFlowSuspendable(unignore) { - session.unIgnoreUserIds(listOf(unignore.userId)) + session.userService().unIgnoreUserIds(listOf(unignore.userId)) } } @@ -790,31 +813,31 @@ class MessageComposerViewModel @AssistedInject constructor( } } rootThreadEventId?.let { - room.replyInThread(it, sequence) - } ?: room.sendTextMessage(sequence) + room.relationService().replyInThread(it, sequence) + } ?: room.sendService().sendTextMessage(sequence) } /** - * Convert a send mode to a draft and save the draft + * Convert a send mode to a draft and save the draft. */ private fun handleSaveTextDraft(draft: String) = withState { session.coroutineScope.launch { when { it.sendMode is SendMode.Regular && !it.sendMode.fromSharing -> { setState { copy(sendMode = it.sendMode.copy(text = draft)) } - room.saveDraft(UserDraft.Regular(draft)) + room.draftService().saveDraft(UserDraft.Regular(draft)) } it.sendMode is SendMode.Reply -> { setState { copy(sendMode = it.sendMode.copy(text = draft)) } - room.saveDraft(UserDraft.Reply(it.sendMode.timelineEvent.root.eventId!!, draft)) + room.draftService().saveDraft(UserDraft.Reply(it.sendMode.timelineEvent.root.eventId!!, draft)) } it.sendMode is SendMode.Quote -> { setState { copy(sendMode = it.sendMode.copy(text = draft)) } - room.saveDraft(UserDraft.Quote(it.sendMode.timelineEvent.root.eventId!!, draft)) + room.draftService().saveDraft(UserDraft.Quote(it.sendMode.timelineEvent.root.eventId!!, draft)) } it.sendMode is SendMode.Edit -> { setState { copy(sendMode = it.sendMode.copy(text = draft)) } - room.saveDraft(UserDraft.Edit(it.sendMode.timelineEvent.root.eventId!!, draft)) + room.draftService().saveDraft(UserDraft.Edit(it.sendMode.timelineEvent.root.eventId!!, draft)) } } } @@ -835,11 +858,12 @@ class MessageComposerViewModel @AssistedInject constructor( } else { audioMessageHelper.stopRecording(convertForSending = true)?.let { audioType -> if (audioType.duration > 1000) { - room.sendMedia( + room.sendService().sendMedia( attachment = audioType.toContentAttachmentData(isVoiceMessage = true), compressBeforeSending = false, roomIds = emptySet(), - rootThreadEventId = rootThreadEventId) + rootThreadEventId = rootThreadEventId + ) } else { audioMessageHelper.deleteRecording() } @@ -902,7 +926,7 @@ class MessageComposerViewModel @AssistedInject constructor( viewModelScope.launch { playingAudioContent?.toContentAttachmentData()?.let { voiceDraft -> val content = voiceDraft.toJsonString() - room.saveDraft(UserDraft.Voice(content)) + room.draftService().saveDraft(UserDraft.Voice(content)) setState { copy(sendMode = SendMode.Voice(content)) } } } diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/composer/MessageComposerViewState.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/composer/MessageComposerViewState.kt index 95553eb1cd..75a6c1e912 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/composer/MessageComposerViewState.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/composer/MessageComposerViewState.kt @@ -21,21 +21,22 @@ import im.vector.app.features.home.room.detail.arguments.TimelineArgs import im.vector.app.features.home.room.detail.composer.voice.VoiceMessageRecorderView import org.matrix.android.sdk.api.extensions.orFalse import org.matrix.android.sdk.api.session.room.timeline.TimelineEvent +import kotlin.random.Random /** * Describes the current send mode: - * REGULAR: sends the text as a regular message - * QUOTE: User is currently quoting a message - * EDIT: User is currently editing an existing message + * REGULAR: sends the text as a regular message; + * QUOTE: User is currently quoting a message; + * EDIT: User is currently editing an existing message. * - * Depending on the state the bottom toolbar will change (icons/preview/actions...) + * Depending on the state the bottom toolbar will change (icons/preview/actions...). */ sealed interface SendMode { data class Regular( val text: String, val fromSharing: Boolean, // This is necessary for forcing refresh on selectSubscribe - private val ts: Long = System.currentTimeMillis() + private val random: Int = Random.nextInt() ) : SendMode data class Quote(val timelineEvent: TimelineEvent, val text: String) : SendMode @@ -83,7 +84,8 @@ data class MessageComposerViewState( constructor(args: TimelineArgs) : this( roomId = args.roomId, startsThread = args.threadTimelineArgs?.startsThread.orFalse(), - rootThreadEventId = args.threadTimelineArgs?.rootThreadEventId) + rootThreadEventId = args.threadTimelineArgs?.rootThreadEventId + ) fun isInThreadTimeline(): Boolean = rootThreadEventId != null } diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/composer/voice/VoiceMessageRecorderView.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/composer/voice/VoiceMessageRecorderView.kt index b898aaf114..4350ad6a7d 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/composer/voice/VoiceMessageRecorderView.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/composer/voice/VoiceMessageRecorderView.kt @@ -225,7 +225,7 @@ class VoiceMessageRecorderView @JvmOverloads constructor( override fun onUpdate(state: AudioMessagePlaybackTracker.Listener.State) { when (state) { is AudioMessagePlaybackTracker.Listener.State.Recording -> { - voiceMessageViews.renderRecordingWaveform(state.amplitudeList.toTypedArray()) + voiceMessageViews.renderRecordingWaveform(state.amplitudeList.toList()) } is AudioMessagePlaybackTracker.Listener.State.Playing -> { voiceMessageViews.renderPlaying(state) diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/composer/voice/VoiceMessageViews.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/composer/voice/VoiceMessageViews.kt index 0256064af2..0a093221a6 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/composer/voice/VoiceMessageViews.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/composer/voice/VoiceMessageViews.kt @@ -345,10 +345,10 @@ class VoiceMessageViews( } } - fun renderRecordingWaveform(amplitudeList: Array) { + fun renderRecordingWaveform(amplitudeList: List) { views.voicePlaybackWaveform.doOnLayout { waveFormView -> val waveformColor = ThemeUtils.getColor(waveFormView.context, R.attr.vctr_content_quaternary) - amplitudeList.iterator().forEach { + amplitudeList.forEach { (waveFormView as AudioWaveformView).add(AudioWaveformView.FFT(it.toFloat(), waveformColor)) } } diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/readreceipts/DisplayReadReceiptsBottomSheet.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/readreceipts/DisplayReadReceiptsBottomSheet.kt index db80b02a6b..dfb23d25c8 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/readreceipts/DisplayReadReceiptsBottomSheet.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/readreceipts/DisplayReadReceiptsBottomSheet.kt @@ -40,7 +40,7 @@ data class DisplayReadReceiptArgs( ) : Parcelable /** - * Bottom sheet displaying list of read receipts for a given event ordered by descending timestamp + * Bottom sheet displaying list of read receipts for a given event ordered by descending timestamp. */ @AndroidEntryPoint class DisplayReadReceiptsBottomSheet : diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/readreceipts/DisplayReadReceiptsController.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/readreceipts/DisplayReadReceiptsController.kt index bf88218fa6..5246906223 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/readreceipts/DisplayReadReceiptsController.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/readreceipts/DisplayReadReceiptsController.kt @@ -26,12 +26,12 @@ import org.matrix.android.sdk.api.session.Session import javax.inject.Inject /** - * Epoxy controller for read receipt event list + * Epoxy controller for read receipt event list. */ class DisplayReadReceiptsController @Inject constructor(private val dateFormatter: VectorDateFormatter, private val session: Session, private val avatarRender: AvatarRenderer) : - TypedEpoxyController>() { + TypedEpoxyController>() { var listener: Listener? = null diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/search/SearchFragment.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/search/SearchFragment.kt index b3543ae579..011258f126 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/search/SearchFragment.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/search/SearchFragment.kt @@ -37,6 +37,7 @@ import im.vector.app.core.extensions.trackItemsVisibilityChange import im.vector.app.core.platform.StateView import im.vector.app.core.platform.VectorBaseFragment import im.vector.app.databinding.FragmentSearchBinding +import im.vector.app.features.analytics.plan.ViewRoom import im.vector.app.features.home.room.threads.arguments.ThreadTimelineArgs import kotlinx.parcelize.Parcelize import org.matrix.android.sdk.api.session.events.model.Event @@ -97,7 +98,8 @@ class SearchFragment @Inject constructor( is Success -> { views.stateView.state = StateView.State.Empty( title = getString(R.string.search_no_results), - image = ContextCompat.getDrawable(requireContext(), R.drawable.ic_search_no_results)) + image = ContextCompat.getDrawable(requireContext(), R.drawable.ic_search_no_results) + ) } else -> Unit } @@ -119,22 +121,43 @@ class SearchFragment @Inject constructor( override fun onItemClicked(event: Event) = navigateToEvent(event) + override fun onThreadSummaryClicked(event: Event) { + navigateToEvent(event, true) + } + /** * Navigate and highlight the event. If this is a thread event, * user will be redirected to the appropriate thread room * @param event the event to navigate and highlight + * @param forceNavigateToThread force navigate within the thread (ex. when user clicks on thread summary) */ - private fun navigateToEvent(event: Event) { + private fun navigateToEvent(event: Event, forceNavigateToThread: Boolean = false) { val roomId = event.roomId ?: return - event.getRootThreadEventId()?.let { + val rootThreadEventId = if (forceNavigateToThread) { + event.eventId + } else { + event.getRootThreadEventId() + } + + rootThreadEventId?.let { val threadTimelineArgs = ThreadTimelineArgs( roomId = roomId, displayName = fragmentArgs.roomDisplayName, avatarUrl = fragmentArgs.roomAvatarUrl, roomEncryptionTrustLevel = null, - rootThreadEventId = it) + rootThreadEventId = it + ) navigator.openThread(requireContext(), threadTimelineArgs, event.eventId) - } ?: navigator.openRoom(requireContext(), roomId, event.eventId) + } ?: openRoom(roomId, event.eventId) + } + + private fun openRoom(roomId: String, eventId: String?) { + navigator.openRoom( + context = requireContext(), + roomId = roomId, + eventId = eventId, + trigger = ViewRoom.Trigger.MessageSearch + ) } override fun loadMore() { diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/search/SearchResultController.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/search/SearchResultController.kt index 5b1f17cfe2..81e4d8fd5f 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/search/SearchResultController.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/search/SearchResultController.kt @@ -30,6 +30,7 @@ import im.vector.app.core.epoxy.loadingItem import im.vector.app.core.epoxy.noResultItem import im.vector.app.core.resources.StringProvider import im.vector.app.core.resources.UserPreferencesProvider +import im.vector.app.core.time.Clock import im.vector.app.core.ui.list.GenericHeaderItem_ import im.vector.app.features.home.AvatarRenderer import im.vector.app.features.home.room.detail.timeline.format.DisplayableEventFormatter @@ -47,7 +48,8 @@ class SearchResultController @Inject constructor( private val stringProvider: StringProvider, private val dateFormatter: VectorDateFormatter, private val displayableEventFormatter: DisplayableEventFormatter, - private val userPreferencesProvider: UserPreferencesProvider + private val userPreferencesProvider: UserPreferencesProvider, + private val clock: Clock, ) : TypedEpoxyController() { var listener: Listener? = null @@ -56,6 +58,7 @@ class SearchResultController @Inject constructor( interface Listener { fun onItemClicked(event: Event) + fun onThreadSummaryClicked(event: Event) fun loadMore() } @@ -109,7 +112,7 @@ class SearchResultController @Inject constructor( val spannable = setHighLightedText(text, data.highlights) ?: return@forEach val eventDate = Calendar.getInstance().apply { - timeInMillis = eventAndSender.event.originServerTs ?: System.currentTimeMillis() + timeInMillis = eventAndSender.event.originServerTs ?: clock.epochMillis() } if (lastDate?.get(Calendar.DAY_OF_YEAR) != eventDate.get(Calendar.DAY_OF_YEAR)) { GenericHeaderItem_() @@ -124,12 +127,15 @@ class SearchResultController @Inject constructor( .avatarRenderer(avatarRenderer) .formattedDate(dateFormatter.format(event.originServerTs, DateFormatKind.MESSAGE_SIMPLE)) .spannable(spannable.toEpoxyCharSequence()) - .sender(eventAndSender.sender - ?: eventAndSender.event.senderId?.let { session.getRoomMember(it, data.roomId) }?.toMatrixItem()) + .sender( + eventAndSender.sender + ?: eventAndSender.event.senderId?.let { session.roomService().getRoomMember(it, data.roomId) }?.toMatrixItem() + ) .threadDetails(event.threadDetails) .threadSummaryFormatted(displayableEventFormatter.formatThreadSummary(event.threadDetails?.threadSummaryLatestEvent).toString()) .areThreadMessagesEnabled(userPreferencesProvider.areThreadMessagesEnabled()) .listener { listener?.onItemClicked(eventAndSender.event) } + .threadSummaryListener { listener?.onThreadSummaryClicked(eventAndSender.event) } .let { result.add(it) } } diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/search/SearchResultItem.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/search/SearchResultItem.kt index 3e141ab0e9..d92dcdd3ef 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/search/SearchResultItem.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/search/SearchResultItem.kt @@ -46,6 +46,7 @@ abstract class SearchResultItem : VectorEpoxyModel() { @EpoxyAttribute var areThreadMessagesEnabled: Boolean = false @EpoxyAttribute(EpoxyAttribute.Option.DoNotHash) var listener: ClickListener? = null + @EpoxyAttribute(EpoxyAttribute.Option.DoNotHash) var threadSummaryListener: ClickListener? = null override fun bind(holder: Holder) { super.bind(holder) @@ -66,23 +67,24 @@ abstract class SearchResultItem : VectorEpoxyModel() { val displayName = it.threadSummarySenderInfo?.displayName val avatarUrl = it.threadSummarySenderInfo?.avatarUrl avatarRenderer.render(MatrixItem.UserItem(userId, displayName, avatarUrl), holder.threadSummaryAvatarImageView) + holder.threadSummaryContainer.onClick(threadSummaryListener) } else { showFromThread(holder) } } ?: run { - holder.threadSummaryConstraintLayout.isVisible = false + holder.threadSummaryContainer.isVisible = false holder.fromThreadConstraintLayout.isVisible = false } } } private fun showThreadSummary(holder: Holder, show: Boolean = true) { - holder.threadSummaryConstraintLayout.isVisible = show + holder.threadSummaryContainer.isVisible = show holder.fromThreadConstraintLayout.isVisible = !show } private fun showFromThread(holder: Holder, show: Boolean = true) { - holder.threadSummaryConstraintLayout.isVisible = !show + holder.threadSummaryContainer.isVisible = !show holder.fromThreadConstraintLayout.isVisible = show } @@ -91,7 +93,7 @@ abstract class SearchResultItem : VectorEpoxyModel() { val memberNameView by bind(R.id.messageMemberNameView) val timeView by bind(R.id.messageTimeView) val contentView by bind(R.id.messageContentView) - val threadSummaryConstraintLayout by bind(R.id.searchThreadSummaryConstraintLayout) + val threadSummaryContainer by bind(R.id.searchThreadSummaryContainer) val threadSummaryCounterTextView by bind(R.id.messageThreadSummaryCounterTextView) val threadSummaryAvatarImageView by bind(R.id.messageThreadSummaryAvatarImageView) val threadSummaryInfoTextView by bind(R.id.messageThreadSummaryInfoTextView) diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/search/SearchViewModel.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/search/SearchViewModel.kt index 1702fb95cd..561023401f 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/search/SearchViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/search/SearchViewModel.kt @@ -30,11 +30,12 @@ import kotlinx.coroutines.CancellationException import kotlinx.coroutines.Job import kotlinx.coroutines.launch import org.matrix.android.sdk.api.session.Session +import org.matrix.android.sdk.api.session.getRoom import org.matrix.android.sdk.api.session.search.SearchResult class SearchViewModel @AssistedInject constructor( @Assisted private val initialState: SearchViewState, - session: Session + private val session: Session ) : VectorViewModel(initialState) { private val room = session.getRoom(initialState.roomId) @@ -100,8 +101,9 @@ class SearchViewModel @AssistedInject constructor( currentTask = viewModelScope.launch { try { - val result = room.search( + val result = session.searchService().search( searchTerm = state.searchTerm, + roomId = initialState.roomId, nextBatch = nextBatch, orderByRecent = true, beforeLimit = 0, diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/TimelineEventController.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/TimelineEventController.kt index 981e5740d7..a601870ae8 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/TimelineEventController.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/TimelineEventController.kt @@ -31,6 +31,7 @@ import im.vector.app.core.epoxy.LoadingItem_ import im.vector.app.core.extensions.localDateTime import im.vector.app.core.extensions.nextOrNull import im.vector.app.core.extensions.prevOrNull +import im.vector.app.core.time.Clock import im.vector.app.features.home.room.detail.JitsiState import im.vector.app.features.home.room.detail.RoomDetailAction import im.vector.app.features.home.room.detail.RoomDetailViewState @@ -78,23 +79,25 @@ import javax.inject.Inject import kotlin.math.min import kotlin.system.measureTimeMillis -class TimelineEventController @Inject constructor(private val dateFormatter: VectorDateFormatter, - private val vectorPreferences: VectorPreferences, - private val contentUploadStateTrackerBinder: ContentUploadStateTrackerBinder, - private val contentDownloadStateTrackerBinder: ContentDownloadStateTrackerBinder, - private val timelineItemFactory: TimelineItemFactory, - private val timelineMediaSizeProvider: TimelineMediaSizeProvider, - private val mergedHeaderItemFactory: MergedHeaderItemFactory, - private val session: Session, - @TimelineEventControllerHandler - private val backgroundHandler: Handler, - private val timelineEventVisibilityHelper: TimelineEventVisibilityHelper, - private val readReceiptsItemFactory: ReadReceiptsItemFactory, - private val reactionListFactory: ReactionsSummaryFactory +class TimelineEventController @Inject constructor( + private val dateFormatter: VectorDateFormatter, + private val vectorPreferences: VectorPreferences, + private val contentUploadStateTrackerBinder: ContentUploadStateTrackerBinder, + private val contentDownloadStateTrackerBinder: ContentDownloadStateTrackerBinder, + private val timelineItemFactory: TimelineItemFactory, + private val timelineMediaSizeProvider: TimelineMediaSizeProvider, + private val mergedHeaderItemFactory: MergedHeaderItemFactory, + private val session: Session, + @TimelineEventControllerHandler + private val backgroundHandler: Handler, + private val timelineEventVisibilityHelper: TimelineEventVisibilityHelper, + private val readReceiptsItemFactory: ReadReceiptsItemFactory, + private val reactionListFactory: ReactionsSummaryFactory, + private val clock: Clock, ) : EpoxyController(backgroundHandler, backgroundHandler), Timeline.Listener, EpoxyController.Interceptor { /** - * This is a partial state of the RoomDetailViewState + * This is a partial state of the RoomDetailViewState. */ data class PartialState( val unreadState: UnreadState = UnreadState.Unknown, @@ -132,6 +135,7 @@ class TimelineEventController @Inject constructor(private val dateFormatter: Vec mediaData: ImageContentRenderer.Data, view: View, inMemory: List) + fun onVideoMessageClicked(messageVideoContent: MessageVideoContent, mediaData: VideoContentRenderer.Data, view: View) // fun onFileMessageClicked(eventId: String, messageFileContent: MessageFileContent) @@ -208,8 +212,10 @@ class TimelineEventController @Inject constructor(private val dateFormatter: Vec override fun onChanged(position: Int, count: Int, payload: Any?) { synchronized(modelCache) { assertUpdateCallbacksAllowed() - Timber.v("listUpdateCallback.onChanged(position: $position, count: $count). " + - "\ncurrentSnapshot has size of ${currentSnapshot.size} items") + Timber.v( + "listUpdateCallback.onChanged(position: $position, count: $count). " + + "\ncurrentSnapshot has size of ${currentSnapshot.size} items" + ) (position until position + count).forEach { // Invalidate cache modelCache[it] = null @@ -237,8 +243,10 @@ class TimelineEventController @Inject constructor(private val dateFormatter: Vec override fun onMoved(fromPosition: Int, toPosition: Int) { synchronized(modelCache) { assertUpdateCallbacksAllowed() - Timber.v("listUpdateCallback.onMoved(fromPosition: $fromPosition, toPosition: $toPosition). " + - "\ncurrentSnapshot has size of ${currentSnapshot.size} items") + Timber.v( + "listUpdateCallback.onMoved(fromPosition: $fromPosition, toPosition: $toPosition). " + + "\ncurrentSnapshot has size of ${currentSnapshot.size} items" + ) val model = modelCache.removeAt(fromPosition) modelCache.add(toPosition, model) requestModelBuild() @@ -248,8 +256,10 @@ class TimelineEventController @Inject constructor(private val dateFormatter: Vec override fun onInserted(position: Int, count: Int) { synchronized(modelCache) { assertUpdateCallbacksAllowed() - Timber.v("listUpdateCallback.onInserted(position: $position, count: $count). " + - "\ncurrentSnapshot has size of ${currentSnapshot.size} items") + Timber.v( + "listUpdateCallback.onInserted(position: $position, count: $count). " + + "\ncurrentSnapshot has size of ${currentSnapshot.size} items" + ) repeat(count) { modelCache.add(position, null) } @@ -260,8 +270,10 @@ class TimelineEventController @Inject constructor(private val dateFormatter: Vec override fun onRemoved(position: Int, count: Int) { synchronized(modelCache) { assertUpdateCallbacksAllowed() - Timber.v("listUpdateCallback.onRemoved(position: $position, count: $count). " + - "\ncurrentSnapshot has size of ${currentSnapshot.size} items") + Timber.v( + "listUpdateCallback.onRemoved(position: $position, count: $count). " + + "\ncurrentSnapshot has size of ${currentSnapshot.size} items" + ) repeat(count) { modelCache.removeAt(position) } @@ -313,7 +325,7 @@ class TimelineEventController @Inject constructor(private val dateFormatter: Vec if (partialState.roomSummary?.membership != Membership.JOIN) { return } - val timestamp = System.currentTimeMillis() + val timestamp = clock.epochMillis() val showingForwardLoader = LoadingItem_() .id("forward_loading_item_$timestamp") @@ -405,14 +417,16 @@ class TimelineEventController @Inject constructor(private val dateFormatter: Vec timelineEvent = it, highlightedEventId = partialState.highlightedEventId, isFromThreadTimeline = partialState.isFromThreadTimeline(), - rootThreadEventId = partialState.rootThreadEventId) + rootThreadEventId = partialState.rootThreadEventId + ) } val nextDisplayableEvent = currentSnapshot.subList(position + 1, currentSnapshot.size).firstOrNull { timelineEventVisibilityHelper.shouldShowEvent( timelineEvent = it, highlightedEventId = partialState.highlightedEventId, isFromThreadTimeline = partialState.isFromThreadTimeline(), - rootThreadEventId = partialState.rootThreadEventId) + rootThreadEventId = partialState.rootThreadEventId + ) } val timelineEventsGroup = timelineEventsGroups.getOrNull(event) val params = TimelineItemFactoryParams( @@ -465,7 +479,8 @@ class TimelineEventController @Inject constructor(private val dateFormatter: Vec position: Int, receiptsByEvents: Map>): CacheItemData { val wantsDateSeparator = wantsDateSeparator(event, nextEvent) - val mergedHeaderModel = mergedHeaderItemFactory.create(event, + val mergedHeaderModel = mergedHeaderItemFactory.create( + event, nextEvent = nextEvent, partialState = partialState, items = this@TimelineEventController.currentSnapshot, @@ -536,7 +551,8 @@ class TimelineEventController @Inject constructor(private val dateFormatter: Vec timelineEvent = event, highlightedEventId = partialState.highlightedEventId, isFromThreadTimeline = partialState.isFromThreadTimeline(), - rootThreadEventId = partialState.rootThreadEventId)) { + rootThreadEventId = partialState.rootThreadEventId + )) { lastShownEventId = event.eventId } if (lastShownEventId == null) { @@ -593,7 +609,7 @@ class TimelineEventController @Inject constructor(private val dateFormatter: Vec } /** - * Return true if added + * Return true if added. */ private fun LoadingItem_.addWhenLoading(direction: Timeline.Direction): Boolean { val host = this@TimelineEventController diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/action/LocationUiData.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/action/LocationUiData.kt index c50c649221..073dda626f 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/action/LocationUiData.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/action/LocationUiData.kt @@ -19,7 +19,7 @@ package im.vector.app.features.home.room.detail.timeline.action import im.vector.app.features.home.room.detail.timeline.helper.LocationPinProvider /** - * Data used to display Location data in the message bottom sheet + * Data used to display Location data in the message bottom sheet. */ data class LocationUiData( val locationUrl: String, diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/action/MessageActionState.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/action/MessageActionState.kt index 0cf7e60eae..f547734651 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/action/MessageActionState.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/action/MessageActionState.kt @@ -25,7 +25,7 @@ import org.matrix.android.sdk.api.session.room.send.SendState import org.matrix.android.sdk.api.session.room.timeline.TimelineEvent /** - * Quick reactions state + * Quick reactions state. */ data class ToggleState( val reaction: String, @@ -57,7 +57,8 @@ data class MessageActionState( roomId = args.roomId, eventId = args.eventId, informationData = args.informationData, - isFromThreadTimeline = args.isFromThreadTimeline) + isFromThreadTimeline = args.isFromThreadTimeline + ) fun senderName(): String = informationData.memberName?.toString() ?: "" diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/action/MessageActionsAnimator.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/action/MessageActionsAnimator.kt index 5530abf839..a94cca168d 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/action/MessageActionsAnimator.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/action/MessageActionsAnimator.kt @@ -21,7 +21,7 @@ import androidx.recyclerview.widget.DefaultItemAnimator private const val ANIM_DURATION_IN_MILLIS = 300L /** - * We only want to animate the expand of the "Report content" submenu + * We only want to animate the expand of the "Report content" submenu. */ class MessageActionsAnimator : DefaultItemAnimator() { diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/action/MessageActionsBottomSheet.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/action/MessageActionsBottomSheet.kt index 24c5679438..53d9e2aa99 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/action/MessageActionsBottomSheet.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/action/MessageActionsBottomSheet.kt @@ -30,7 +30,7 @@ import im.vector.app.features.home.room.detail.timeline.item.MessageInformationD import javax.inject.Inject /** - * Bottom sheet fragment that shows a message preview with list of contextual actions + * Bottom sheet fragment that shows a message preview with list of contextual actions. */ @AndroidEntryPoint class MessageActionsBottomSheet : diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/action/MessageActionsEpoxyController.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/action/MessageActionsEpoxyController.kt index 41916c609d..ade08fdfed 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/action/MessageActionsEpoxyController.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/action/MessageActionsEpoxyController.kt @@ -54,7 +54,7 @@ import org.matrix.android.sdk.api.session.room.send.SendState import javax.inject.Inject /** - * Epoxy controller for message action list + * Epoxy controller for message action list. */ class MessageActionsEpoxyController @Inject constructor( private val stringProvider: StringProvider, diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/action/MessageActionsViewModel.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/action/MessageActionsViewModel.kt index 9a73afd897..4bc513284b 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/action/MessageActionsViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/action/MessageActionsViewModel.kt @@ -48,6 +48,7 @@ import org.matrix.android.sdk.api.session.events.model.isAttachmentMessage import org.matrix.android.sdk.api.session.events.model.isTextMessage import org.matrix.android.sdk.api.session.events.model.isThread import org.matrix.android.sdk.api.session.events.model.toModel +import org.matrix.android.sdk.api.session.getRoom import org.matrix.android.sdk.api.session.room.model.message.MessageContent import org.matrix.android.sdk.api.session.room.model.message.MessageFormat import org.matrix.android.sdk.api.session.room.model.message.MessagePollContent @@ -376,19 +377,23 @@ class MessageActionsViewModel @AssistedInject constructor( if (canRedact(timelineEvent, actionPermissions)) { if (timelineEvent.root.getClearType() in EventType.POLL_START) { - add(EventSharedAction.Redact( - eventId, - askForReason = informationData.senderId != session.myUserId, - dialogTitleRes = R.string.delete_poll_dialog_title, - dialogDescriptionRes = R.string.delete_poll_dialog_content - )) + add( + EventSharedAction.Redact( + eventId, + askForReason = informationData.senderId != session.myUserId, + dialogTitleRes = R.string.delete_poll_dialog_title, + dialogDescriptionRes = R.string.delete_poll_dialog_content + ) + ) } else { - add(EventSharedAction.Redact( - eventId, - askForReason = informationData.senderId != session.myUserId, - dialogTitleRes = R.string.delete_event_dialog_title, - dialogDescriptionRes = R.string.delete_event_dialog_content - )) + add( + EventSharedAction.Redact( + eventId, + askForReason = informationData.senderId != session.myUserId, + dialogTitleRes = R.string.delete_event_dialog_title, + dialogDescriptionRes = R.string.delete_event_dialog_content + ) + ) } } } @@ -445,13 +450,17 @@ class MessageActionsViewModel @AssistedInject constructor( /** * Determine whether or not the Reply In Thread bottom sheet action will be visible - * to the user + * to the user. */ private fun canReplyInThread(event: TimelineEvent, messageContent: MessageContent?, actionPermissions: ActionPermissions): Boolean { // We let reply in thread visible even if threads are not enabled, with an enhanced flow to attract users // if (!vectorPreferences.areThreadMessagesEnabled()) return false + // Disable beta prompt if the homeserver do not support threads + if (!vectorPreferences.areThreadMessagesEnabled() && + !session.homeServerCapabilitiesService().getHomeServerCapabilities().canUseThreading) return false + if (initialState.isFromThreadTimeline) return false if (event.root.isThread()) return false if (event.root.getClearType() != EventType.MESSAGE && @@ -472,7 +481,7 @@ class MessageActionsViewModel @AssistedInject constructor( } /** - * Determine whether or not the view in room action will be available for the current event + * Determine whether or not the view in room action will be available for the current event. */ private fun canViewInRoom(event: TimelineEvent, messageContent: MessageContent?, diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/action/MessageSharedActionViewModel.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/action/MessageSharedActionViewModel.kt index b6023333b1..a06f85ad3f 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/action/MessageSharedActionViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/action/MessageSharedActionViewModel.kt @@ -19,7 +19,7 @@ import im.vector.app.core.platform.VectorSharedActionViewModel import javax.inject.Inject /** - * Activity shared view model to handle message actions + * Activity shared view model to handle message actions. */ class MessageSharedActionViewModel @Inject constructor() : VectorSharedActionViewModel() { var pendingAction: EventSharedAction? = null diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/edithistory/ViewEditHistoryBottomSheet.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/edithistory/ViewEditHistoryBottomSheet.kt index 321745355e..d01a607d17 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/edithistory/ViewEditHistoryBottomSheet.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/edithistory/ViewEditHistoryBottomSheet.kt @@ -32,7 +32,7 @@ import im.vector.app.features.home.room.detail.timeline.item.MessageInformationD import javax.inject.Inject /** - * Bottom sheet displaying list of edits for a given event ordered by timestamp + * Bottom sheet displaying list of edits for a given event ordered by timestamp. */ @AndroidEntryPoint class ViewEditHistoryBottomSheet : @@ -51,7 +51,8 @@ class ViewEditHistoryBottomSheet : views.bottomSheetRecyclerView.configureWith( epoxyController, dividerDrawable = R.drawable.divider_horizontal_on_secondary, - hasFixedSize = false) + hasFixedSize = false + ) views.bottomSheetTitle.text = context?.getString(R.string.message_edits) } @@ -68,11 +69,13 @@ class ViewEditHistoryBottomSheet : companion object { fun newInstance(roomId: String, informationData: MessageInformationData): ViewEditHistoryBottomSheet { return ViewEditHistoryBottomSheet().apply { - setArguments(TimelineEventFragmentArgs( - eventId = informationData.eventId, - roomId = roomId, - informationData = informationData - )) + setArguments( + TimelineEventFragmentArgs( + eventId = informationData.eventId, + roomId = roomId, + informationData = informationData + ) + ) } } } diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/edithistory/ViewEditHistoryEpoxyController.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/edithistory/ViewEditHistoryEpoxyController.kt index 63a34fe713..b92548dd5e 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/edithistory/ViewEditHistoryEpoxyController.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/edithistory/ViewEditHistoryEpoxyController.kt @@ -26,6 +26,7 @@ import im.vector.app.core.date.DateFormatKind import im.vector.app.core.date.VectorDateFormatter import im.vector.app.core.resources.ColorProvider import im.vector.app.core.resources.StringProvider +import im.vector.app.core.time.Clock import im.vector.app.core.ui.list.genericFooterItem import im.vector.app.core.ui.list.genericHeaderItem import im.vector.app.core.ui.list.genericItem @@ -43,13 +44,14 @@ import java.util.Calendar import javax.inject.Inject /** - * Epoxy controller for edit history list + * Epoxy controller for edit history list. */ class ViewEditHistoryEpoxyController @Inject constructor( private val stringProvider: StringProvider, private val colorProvider: ColorProvider, private val eventHtmlRenderer: EventHtmlRenderer, - private val dateFormatter: VectorDateFormatter + private val dateFormatter: VectorDateFormatter, + private val clock: Clock, ) : TypedEpoxyController() { override fun buildModels(state: ViewEditHistoryViewState) { @@ -86,7 +88,7 @@ class ViewEditHistoryEpoxyController @Inject constructor( val evDate = Calendar.getInstance().apply { timeInMillis = timelineEvent.originServerTs - ?: System.currentTimeMillis() + ?: clock.epochMillis() } if (lastDate?.get(Calendar.DAY_OF_YEAR) != evDate.get(Calendar.DAY_OF_YEAR)) { // need to display header with day diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/edithistory/ViewEditHistoryViewModel.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/edithistory/ViewEditHistoryViewModel.kt index e1d4d71c6a..c8a3bb8967 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/edithistory/ViewEditHistoryViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/edithistory/ViewEditHistoryViewModel.kt @@ -32,6 +32,7 @@ import org.matrix.android.sdk.api.session.Session import org.matrix.android.sdk.api.session.crypto.MXCryptoError import org.matrix.android.sdk.api.session.crypto.model.OlmDecryptionResult import org.matrix.android.sdk.api.session.events.model.isReply +import org.matrix.android.sdk.api.session.getRoom import timber.log.Timber import java.util.UUID @@ -61,7 +62,7 @@ class ViewEditHistoryViewModel @AssistedInject constructor( viewModelScope.launch { val data = try { - room.fetchEditHistory(eventId) + room.relationService().fetchEditHistory(eventId) } catch (failure: Throwable) { setState { copy(editList = Fail(failure)) diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/edithistory/ViewEditHistoryViewState.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/edithistory/ViewEditHistoryViewState.kt index 61a400d875..dd344c4c82 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/edithistory/ViewEditHistoryViewState.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/edithistory/ViewEditHistoryViewState.kt @@ -27,7 +27,7 @@ data class ViewEditHistoryViewState( val roomId: String, val isOriginalAReply: Boolean = false, val editList: Async> = Uninitialized) : - MavericksState { + MavericksState { constructor(args: TimelineEventFragmentArgs) : this(roomId = args.roomId, eventId = args.eventId) } diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/EncryptionItemFactory.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/EncryptionItemFactory.kt index 89899a52e0..0cbd92f525 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/EncryptionItemFactory.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/EncryptionItemFactory.kt @@ -29,6 +29,7 @@ import org.matrix.android.sdk.api.extensions.orFalse import org.matrix.android.sdk.api.session.Session import org.matrix.android.sdk.api.session.events.model.content.EncryptionEventContent import org.matrix.android.sdk.api.session.events.model.toModel +import org.matrix.android.sdk.api.session.getRoomSummary import javax.inject.Inject class EncryptionItemFactory @Inject constructor( diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/LiveLocationMessageItemFactory.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/LiveLocationMessageItemFactory.kt index 9bc148a562..d233deffb8 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/LiveLocationMessageItemFactory.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/LiveLocationMessageItemFactory.kt @@ -24,7 +24,7 @@ import im.vector.app.features.home.room.detail.timeline.item.AbsMessageItem import im.vector.app.features.home.room.detail.timeline.item.MessageLiveLocationStartItem import im.vector.app.features.home.room.detail.timeline.item.MessageLiveLocationStartItem_ import org.matrix.android.sdk.api.extensions.orFalse -import org.matrix.android.sdk.api.session.room.model.livelocation.LiveLocationBeaconContent +import org.matrix.android.sdk.api.session.room.model.message.MessageBeaconInfoContent import javax.inject.Inject class LiveLocationMessageItemFactory @Inject constructor( @@ -34,19 +34,20 @@ class LiveLocationMessageItemFactory @Inject constructor( ) { fun create( - liveLocationContent: LiveLocationBeaconContent, + beaconInfoContent: MessageBeaconInfoContent, highlight: Boolean, attributes: AbsMessageItem.Attributes, ): VectorEpoxyModel<*>? { // TODO handle location received and stopped states return when { - isLiveRunning(liveLocationContent) -> buildStartLiveItem(highlight, attributes) - else -> null + isLiveRunning(beaconInfoContent) -> buildStartLiveItem(highlight, attributes) + else -> null } } - private fun isLiveRunning(liveLocationContent: LiveLocationBeaconContent): Boolean { - return liveLocationContent.getBestBeaconInfo()?.isLive.orFalse() && liveLocationContent.hasTimedOut.not() + private fun isLiveRunning(beaconInfoContent: MessageBeaconInfoContent): Boolean { + // TODO when we will use aggregatedSummary, check if the live has timed out as well + return beaconInfoContent.isLive.orFalse() } private fun buildStartLiveItem( diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/MergedHeaderItemFactory.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/MergedHeaderItemFactory.kt index c21b67a459..aca2aab174 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/MergedHeaderItemFactory.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/MergedHeaderItemFactory.kt @@ -38,6 +38,8 @@ import org.matrix.android.sdk.api.query.QueryStringValue import org.matrix.android.sdk.api.session.events.model.EventType import org.matrix.android.sdk.api.session.events.model.content.EncryptionEventContent import org.matrix.android.sdk.api.session.events.model.toModel +import org.matrix.android.sdk.api.session.getRoom +import org.matrix.android.sdk.api.session.room.getStateEvent import org.matrix.android.sdk.api.session.room.model.PowerLevelsContent import org.matrix.android.sdk.api.session.room.model.create.RoomCreateContent import org.matrix.android.sdk.api.session.room.powerlevels.PowerLevelsHelper @@ -90,7 +92,8 @@ class MergedHeaderItemFactory @Inject constructor(private val activeSessionHolde 2, eventIdToHighlight, partialState.rootThreadEventId, - partialState.isFromThreadTimeline()) + partialState.isFromThreadTimeline() + ) return if (mergedEvents.isEmpty()) { null } else { diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/MessageItemFactory.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/MessageItemFactory.kt index 1787da2ae7..b960e2c6a9 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/MessageItemFactory.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/MessageItemFactory.kt @@ -31,6 +31,7 @@ import im.vector.app.core.epoxy.VectorEpoxyModel import im.vector.app.core.files.LocalFilesHelper import im.vector.app.core.resources.ColorProvider import im.vector.app.core.resources.StringProvider +import im.vector.app.core.time.Clock import im.vector.app.core.utils.DimensionConverter import im.vector.app.core.utils.containsOnlyEmojis import im.vector.app.features.home.room.detail.timeline.TimelineEventController @@ -98,8 +99,8 @@ import org.matrix.android.sdk.api.session.events.model.RelationType import org.matrix.android.sdk.api.session.events.model.content.EncryptedEventContent import org.matrix.android.sdk.api.session.events.model.isThread import org.matrix.android.sdk.api.session.events.model.toModel -import org.matrix.android.sdk.api.session.room.model.livelocation.LiveLocationBeaconContent import org.matrix.android.sdk.api.session.room.model.message.MessageAudioContent +import org.matrix.android.sdk.api.session.room.model.message.MessageBeaconInfoContent import org.matrix.android.sdk.api.session.room.model.message.MessageContent import org.matrix.android.sdk.api.session.room.model.message.MessageContentWithFormattedBody import org.matrix.android.sdk.api.session.room.model.message.MessageEmoteContent @@ -122,31 +123,32 @@ import org.matrix.android.sdk.api.util.MimeTypes import javax.inject.Inject class MessageItemFactory @Inject constructor( - private val localFilesHelper: LocalFilesHelper, - private val colorProvider: ColorProvider, - private val dimensionConverter: DimensionConverter, - private val timelineMediaSizeProvider: TimelineMediaSizeProvider, - private val htmlRenderer: Lazy, - private val htmlCompressor: VectorHtmlCompressor, - private val textRendererFactory: EventTextRenderer.Factory, - private val stringProvider: StringProvider, - private val imageContentRenderer: ImageContentRenderer, - private val messageInformationDataFactory: MessageInformationDataFactory, - private val messageItemAttributesFactory: MessageItemAttributesFactory, - private val contentUploadStateTrackerBinder: ContentUploadStateTrackerBinder, - private val contentDownloadStateTrackerBinder: ContentDownloadStateTrackerBinder, - private val defaultItemFactory: DefaultItemFactory, - private val noticeItemFactory: NoticeItemFactory, - private val avatarSizeProvider: AvatarSizeProvider, - private val pillsPostProcessorFactory: PillsPostProcessor.Factory, - private val lightweightSettingsStorage: LightweightSettingsStorage, - private val spanUtils: SpanUtils, - private val session: Session, - private val audioMessagePlaybackTracker: AudioMessagePlaybackTracker, - private val locationPinProvider: LocationPinProvider, - private val vectorPreferences: VectorPreferences, - private val urlMapProvider: UrlMapProvider, - private val liveLocationMessageItemFactory: LiveLocationMessageItemFactory, + private val localFilesHelper: LocalFilesHelper, + private val colorProvider: ColorProvider, + private val dimensionConverter: DimensionConverter, + private val timelineMediaSizeProvider: TimelineMediaSizeProvider, + private val htmlRenderer: Lazy, + private val htmlCompressor: VectorHtmlCompressor, + private val textRendererFactory: EventTextRenderer.Factory, + private val stringProvider: StringProvider, + private val imageContentRenderer: ImageContentRenderer, + private val messageInformationDataFactory: MessageInformationDataFactory, + private val messageItemAttributesFactory: MessageItemAttributesFactory, + private val contentUploadStateTrackerBinder: ContentUploadStateTrackerBinder, + private val contentDownloadStateTrackerBinder: ContentDownloadStateTrackerBinder, + private val defaultItemFactory: DefaultItemFactory, + private val noticeItemFactory: NoticeItemFactory, + private val avatarSizeProvider: AvatarSizeProvider, + private val pillsPostProcessorFactory: PillsPostProcessor.Factory, + private val lightweightSettingsStorage: LightweightSettingsStorage, + private val spanUtils: SpanUtils, + private val session: Session, + private val clock: Clock, + private val audioMessagePlaybackTracker: AudioMessagePlaybackTracker, + private val locationPinProvider: LocationPinProvider, + private val vectorPreferences: VectorPreferences, + private val urlMapProvider: UrlMapProvider, + private val liveLocationMessageItemFactory: LiveLocationMessageItemFactory, ) { // TODO inject this properly? @@ -181,7 +183,7 @@ class MessageItemFactory @Inject constructor( return defaultItemFactory.create(malformedText, informationData, highlight, callback) } if (messageContent.relatesTo?.type == RelationType.REPLACE || - event.isEncrypted() && event.root.content.toModel()?.relatesTo?.type == RelationType.REPLACE + event.isEncrypted() && event.root.content.toModel()?.relatesTo?.type == RelationType.REPLACE ) { // This is an edit event, we should display it when debugging as a notice event return noticeItemFactory.create(params) @@ -198,24 +200,24 @@ class MessageItemFactory @Inject constructor( // val all = event.root.toContent() // val ev = all.toModel() val messageItem = when (messageContent) { - is MessageEmoteContent -> buildEmoteMessageItem(messageContent, informationData, highlight, callback, attributes) - is MessageTextContent -> buildItemForTextContent(messageContent, informationData, highlight, callback, attributes) - is MessageImageInfoContent -> buildImageMessageItem(messageContent, informationData, highlight, callback, attributes) - is MessageNoticeContent -> buildNoticeMessageItem(messageContent, informationData, highlight, callback, attributes) - is MessageVideoContent -> buildVideoMessageItem(messageContent, informationData, highlight, callback, attributes) - is MessageFileContent -> buildFileMessageItem(messageContent, highlight, attributes) - is MessageAudioContent -> buildAudioContent(params, messageContent, informationData, highlight, attributes) + is MessageEmoteContent -> buildEmoteMessageItem(messageContent, informationData, highlight, callback, attributes) + is MessageTextContent -> buildItemForTextContent(messageContent, informationData, highlight, callback, attributes) + is MessageImageInfoContent -> buildImageMessageItem(messageContent, informationData, highlight, callback, attributes) + is MessageNoticeContent -> buildNoticeMessageItem(messageContent, informationData, highlight, callback, attributes) + is MessageVideoContent -> buildVideoMessageItem(messageContent, informationData, highlight, callback, attributes) + is MessageFileContent -> buildFileMessageItem(messageContent, highlight, attributes) + is MessageAudioContent -> buildAudioContent(params, messageContent, informationData, highlight, attributes) is MessageVerificationRequestContent -> buildVerificationRequestMessageItem(messageContent, informationData, highlight, callback, attributes) - is MessagePollContent -> buildPollItem(messageContent, informationData, highlight, callback, attributes) - is MessageLocationContent -> { + is MessagePollContent -> buildPollItem(messageContent, informationData, highlight, callback, attributes) + is MessageLocationContent -> { if (vectorPreferences.labsRenderLocationsInTimeline()) { buildLocationItem(messageContent, informationData, highlight, attributes) } else { buildMessageTextItem(messageContent.body, false, informationData, highlight, callback, attributes) } } - is LiveLocationBeaconContent -> liveLocationMessageItemFactory.create(messageContent, highlight, attributes) - else -> buildNotHandledMessageItem(messageContent, informationData, highlight, callback, attributes) + is MessageBeaconInfoContent -> liveLocationMessageItemFactory.create(messageContent, highlight, attributes) + else -> buildNotHandledMessageItem(messageContent, informationData, highlight, callback, attributes) } return messageItem?.apply { layout(informationData.messageLayout.layoutRes) @@ -223,10 +225,10 @@ class MessageItemFactory @Inject constructor( } private fun buildLocationItem( - locationContent: MessageLocationContent, - informationData: MessageInformationData, - highlight: Boolean, - attributes: AbsMessageItem.Attributes, + locationContent: MessageLocationContent, + informationData: MessageInformationData, + highlight: Boolean, + attributes: AbsMessageItem.Attributes, ): MessageLocationItem? { val width = timelineMediaSizeProvider.getMaxSize().first val height = dimensionConverter.dpToPx(MESSAGE_LOCATION_ITEM_HEIGHT_IN_DP) @@ -238,22 +240,22 @@ class MessageItemFactory @Inject constructor( val userId = if (locationContent.isSelfLocation()) informationData.senderId else null return MessageLocationItem_() - .attributes(attributes) - .locationUrl(locationUrl) - .mapWidth(width) - .mapHeight(height) - .userId(userId) - .locationPinProvider(locationPinProvider) - .highlighted(highlight) - .leftGuideline(avatarSizeProvider.leftGuideline) + .attributes(attributes) + .locationUrl(locationUrl) + .mapWidth(width) + .mapHeight(height) + .userId(userId) + .locationPinProvider(locationPinProvider) + .highlighted(highlight) + .leftGuideline(avatarSizeProvider.leftGuideline) } private fun buildPollItem( - pollContent: MessagePollContent, - informationData: MessageInformationData, - highlight: Boolean, - callback: TimelineEventController.Callback?, - attributes: AbsMessageItem.Attributes, + pollContent: MessagePollContent, + informationData: MessageInformationData, + highlight: Boolean, + callback: TimelineEventController.Callback?, + attributes: AbsMessageItem.Attributes, ): PollItem { val pollResponseSummary = informationData.pollResponseAggregatedSummary val pollState = createPollState(informationData, pollResponseSummary, pollContent) @@ -264,16 +266,16 @@ class MessageItemFactory @Inject constructor( val totalVotesText = createTotalVotesText(pollState, pollResponseSummary) return PollItem_() - .attributes(attributes) - .eventId(informationData.eventId) - .pollQuestion(question) - .canVote(pollState.isVotable()) - .totalVotesText(totalVotesText) - .optionViewStates(optionViewStates) - .edited(informationData.hasBeenEdited) - .highlighted(highlight) - .leftGuideline(avatarSizeProvider.leftGuideline) - .callback(callback) + .attributes(attributes) + .eventId(informationData.eventId) + .pollQuestion(question) + .canVote(pollState.isVotable()) + .totalVotesText(totalVotesText) + .optionViewStates(optionViewStates) + .edited(informationData.hasBeenEdited) + .highlighted(highlight) + .leftGuideline(avatarSizeProvider.leftGuideline) + .callback(callback) } private fun createPollState( @@ -281,11 +283,11 @@ class MessageItemFactory @Inject constructor( pollResponseSummary: PollResponseData?, pollContent: MessagePollContent, ): PollState = when { - !informationData.sendState.isSent() -> Sending - pollResponseSummary?.isClosed.orFalse() -> Ended + !informationData.sendState.isSent() -> Sending + pollResponseSummary?.isClosed.orFalse() -> Ended pollContent.getBestPollCreationInfo()?.kind == PollType.UNDISCLOSED -> Undisclosed - pollResponseSummary?.myVote?.isNotEmpty().orFalse() -> Voted(pollResponseSummary?.totalVotes ?: 0) - else -> Ready + pollResponseSummary?.myVote?.isNotEmpty().orFalse() -> Voted(pollResponseSummary?.totalVotes ?: 0) + else -> Ready } private fun List.mapToOptions( @@ -303,11 +305,11 @@ class MessageItemFactory @Inject constructor( val isWinner = winnerVoteCount != 0 && voteCount == winnerVoteCount when (pollState) { - Sending -> PollSending(optionId, optionAnswer) - Ready -> PollReady(optionId, optionAnswer) - is Voted -> PollVoted(optionId, optionAnswer, voteCount, votePercentage, isMyVote) + Sending -> PollSending(optionId, optionAnswer) + Ready -> PollReady(optionId, optionAnswer) + is Voted -> PollVoted(optionId, optionAnswer, voteCount, votePercentage, isMyVote) Undisclosed -> PollUndisclosed(optionId, optionAnswer, isMyVote) - Ended -> PollEnded(optionId, optionAnswer, voteCount, votePercentage, isWinner) + Ended -> PollEnded(optionId, optionAnswer, voteCount, votePercentage, isWinner) } } @@ -327,11 +329,11 @@ class MessageItemFactory @Inject constructor( ): String { val votes = pollResponseSummary?.totalVotes ?: 0 return when { - pollState is Ended -> stringProvider.getQuantityString(R.plurals.poll_total_vote_count_after_ended, votes, votes) + pollState is Ended -> stringProvider.getQuantityString(R.plurals.poll_total_vote_count_after_ended, votes, votes) pollState is Undisclosed -> "" - pollState is Voted -> stringProvider.getQuantityString(R.plurals.poll_total_vote_count_before_ended_and_voted, votes, votes) - votes == 0 -> stringProvider.getString(R.string.poll_no_votes_cast) - else -> stringProvider.getQuantityString(R.plurals.poll_total_vote_count_before_ended_and_not_voted, votes, votes) + pollState is Voted -> stringProvider.getQuantityString(R.plurals.poll_total_vote_count_before_ended_and_voted, votes, votes) + votes == 0 -> stringProvider.getString(R.string.poll_no_votes_cast) + else -> stringProvider.getQuantityString(R.plurals.poll_total_vote_count_before_ended_and_not_voted, votes, votes) } } @@ -406,27 +408,27 @@ class MessageItemFactory @Inject constructor( } return MessageVoiceItem_() - .attributes(attributes) - .duration(messageContent.audioWaveformInfo?.duration ?: 0) - .waveform(messageContent.audioWaveformInfo?.waveform?.toFft().orEmpty()) - .playbackControlButtonClickListener(playbackControlButtonClickListener) - .waveformTouchListener(waveformTouchListener) - .audioMessagePlaybackTracker(audioMessagePlaybackTracker) - .isLocalFile(localFilesHelper.isLocalFile(fileUrl)) - .mxcUrl(fileUrl) - .contentUploadStateTrackerBinder(contentUploadStateTrackerBinder) - .contentDownloadStateTrackerBinder(contentDownloadStateTrackerBinder) - .highlighted(highlight) - .leftGuideline(avatarSizeProvider.leftGuideline) + .attributes(attributes) + .duration(messageContent.audioWaveformInfo?.duration ?: 0) + .waveform(messageContent.audioWaveformInfo?.waveform?.toFft().orEmpty()) + .playbackControlButtonClickListener(playbackControlButtonClickListener) + .waveformTouchListener(waveformTouchListener) + .audioMessagePlaybackTracker(audioMessagePlaybackTracker) + .isLocalFile(localFilesHelper.isLocalFile(fileUrl)) + .mxcUrl(fileUrl) + .contentUploadStateTrackerBinder(contentUploadStateTrackerBinder) + .contentDownloadStateTrackerBinder(contentDownloadStateTrackerBinder) + .highlighted(highlight) + .leftGuideline(avatarSizeProvider.leftGuideline) } private fun buildVerificationRequestMessageItem( - messageContent: MessageVerificationRequestContent, - @Suppress("UNUSED_PARAMETER") - informationData: MessageInformationData, - highlight: Boolean, - callback: TimelineEventController.Callback?, - attributes: AbsMessageItem.Attributes, + messageContent: MessageVerificationRequestContent, + @Suppress("UNUSED_PARAMETER") + informationData: MessageInformationData, + highlight: Boolean, + callback: TimelineEventController.Callback?, + attributes: AbsMessageItem.Attributes, ): VerificationRequestItem? { // If this request is not sent by me or sent to me, we should ignore it in timeline val myUserId = session.myUserId @@ -436,49 +438,50 @@ class MessageItemFactory @Inject constructor( val otherUserId = if (informationData.sentByMe) messageContent.toUserId else informationData.senderId val otherUserName = if (informationData.sentByMe) { - session.getRoomMember(messageContent.toUserId, roomId)?.displayName + session.roomService().getRoomMember(messageContent.toUserId, roomId)?.displayName } else { informationData.memberName } return VerificationRequestItem_() - .attributes( - VerificationRequestItem.Attributes( - otherUserId = otherUserId, - otherUserName = otherUserName.toString(), - referenceId = informationData.eventId, - informationData = informationData, - avatarRenderer = attributes.avatarRenderer, - messageColorProvider = attributes.messageColorProvider, - itemLongClickListener = attributes.itemLongClickListener, - itemClickListener = attributes.itemClickListener, - reactionPillCallback = attributes.reactionPillCallback, - readReceiptsCallback = attributes.readReceiptsCallback, - emojiTypeFace = attributes.emojiTypeFace, - reactionsSummaryEvents = attributes.reactionsSummaryEvents, + .attributes( + VerificationRequestItem.Attributes( + otherUserId = otherUserId, + otherUserName = otherUserName.toString(), + referenceId = informationData.eventId, + informationData = informationData, + avatarRenderer = attributes.avatarRenderer, + messageColorProvider = attributes.messageColorProvider, + itemLongClickListener = attributes.itemLongClickListener, + itemClickListener = attributes.itemClickListener, + reactionPillCallback = attributes.reactionPillCallback, + readReceiptsCallback = attributes.readReceiptsCallback, + emojiTypeFace = attributes.emojiTypeFace, + reactionsSummaryEvents = attributes.reactionsSummaryEvents, + ) ) - ) - .callback(callback) - .highlighted(highlight) - .leftGuideline(avatarSizeProvider.leftGuideline) + .clock(clock) + .callback(callback) + .highlighted(highlight) + .leftGuideline(avatarSizeProvider.leftGuideline) } private fun buildFileMessageItem( - messageContent: MessageFileContent, - highlight: Boolean, - attributes: AbsMessageItem.Attributes, + messageContent: MessageFileContent, + highlight: Boolean, + attributes: AbsMessageItem.Attributes, ): MessageFileItem { val mxcUrl = messageContent.getFileUrl() ?: "" return MessageFileItem_() - .attributes(attributes) - .leftGuideline(avatarSizeProvider.leftGuideline) - .isLocalFile(localFilesHelper.isLocalFile(messageContent.getFileUrl())) - .isDownloaded(session.fileService().isFileInCache(messageContent)) - .mxcUrl(mxcUrl) - .contentUploadStateTrackerBinder(contentUploadStateTrackerBinder) - .contentDownloadStateTrackerBinder(contentDownloadStateTrackerBinder) - .highlighted(highlight) - .filename(messageContent.body) - .iconRes(R.drawable.ic_paperclip) + .attributes(attributes) + .leftGuideline(avatarSizeProvider.leftGuideline) + .isLocalFile(localFilesHelper.isLocalFile(messageContent.getFileUrl())) + .isDownloaded(session.fileService().isFileInCache(messageContent)) + .mxcUrl(mxcUrl) + .contentUploadStateTrackerBinder(contentUploadStateTrackerBinder) + .contentDownloadStateTrackerBinder(contentDownloadStateTrackerBinder) + .highlighted(highlight) + .filename(messageContent.body) + .iconRes(R.drawable.ic_paperclip) } private fun buildAudioContent( @@ -488,10 +491,10 @@ class MessageItemFactory @Inject constructor( highlight: Boolean, attributes: AbsMessageItem.Attributes, ) = if (messageContent.voiceMessageIndicator != null) { - buildVoiceMessageItem(params, messageContent, informationData, highlight, attributes) - } else { - buildAudioMessageItem(params, messageContent, informationData, highlight, attributes) - } + buildVoiceMessageItem(params, messageContent, informationData, highlight, attributes) + } else { + buildAudioMessageItem(params, messageContent, informationData, highlight, attributes) + } private fun buildNotHandledMessageItem( messageContent: MessageContent, @@ -504,95 +507,95 @@ class MessageItemFactory @Inject constructor( } private fun buildImageMessageItem( - messageContent: MessageImageInfoContent, - @Suppress("UNUSED_PARAMETER") - informationData: MessageInformationData, - highlight: Boolean, - callback: TimelineEventController.Callback?, - attributes: AbsMessageItem.Attributes, + messageContent: MessageImageInfoContent, + @Suppress("UNUSED_PARAMETER") + informationData: MessageInformationData, + highlight: Boolean, + callback: TimelineEventController.Callback?, + attributes: AbsMessageItem.Attributes, ): MessageImageVideoItem? { val (maxWidth, maxHeight) = timelineMediaSizeProvider.getMaxSize() val data = ImageContentRenderer.Data( - eventId = informationData.eventId, - filename = messageContent.body, - mimeType = messageContent.mimeType, - url = messageContent.getFileUrl(), - elementToDecrypt = messageContent.encryptedFileInfo?.toElementToDecrypt(), - height = messageContent.info?.height, - maxHeight = maxHeight, - width = messageContent.info?.width, - maxWidth = maxWidth, - allowNonMxcUrls = informationData.sendState.isSending() + eventId = informationData.eventId, + filename = messageContent.body, + mimeType = messageContent.mimeType, + url = messageContent.getFileUrl(), + elementToDecrypt = messageContent.encryptedFileInfo?.toElementToDecrypt(), + height = messageContent.info?.height, + maxHeight = maxHeight, + width = messageContent.info?.width, + maxWidth = maxWidth, + allowNonMxcUrls = informationData.sendState.isSending() ) return MessageImageVideoItem_() - .attributes(attributes) - .leftGuideline(avatarSizeProvider.leftGuideline) - .imageContentRenderer(imageContentRenderer) - .contentUploadStateTrackerBinder(contentUploadStateTrackerBinder) - .playable(messageContent.mimeType == MimeTypes.Gif) - .highlighted(highlight) - .mediaData(data) - .apply { - if (messageContent.msgType == MessageType.MSGTYPE_STICKER_LOCAL) { - mode(ImageContentRenderer.Mode.STICKER) - clickListener { view -> - callback?.onImageMessageClicked(messageContent, data, view, listOf(data)) - } - } else { - clickListener { view -> - callback?.onImageMessageClicked(messageContent, data, view, emptyList()) + .attributes(attributes) + .leftGuideline(avatarSizeProvider.leftGuideline) + .imageContentRenderer(imageContentRenderer) + .contentUploadStateTrackerBinder(contentUploadStateTrackerBinder) + .playable(messageContent.mimeType == MimeTypes.Gif) + .highlighted(highlight) + .mediaData(data) + .apply { + if (messageContent.msgType == MessageType.MSGTYPE_STICKER_LOCAL) { + mode(ImageContentRenderer.Mode.STICKER) + clickListener { view -> + callback?.onImageMessageClicked(messageContent, data, view, listOf(data)) + } + } else { + clickListener { view -> + callback?.onImageMessageClicked(messageContent, data, view, emptyList()) + } } } - } } private fun buildVideoMessageItem( - messageContent: MessageVideoContent, - informationData: MessageInformationData, - highlight: Boolean, - callback: TimelineEventController.Callback?, - attributes: AbsMessageItem.Attributes, + messageContent: MessageVideoContent, + informationData: MessageInformationData, + highlight: Boolean, + callback: TimelineEventController.Callback?, + attributes: AbsMessageItem.Attributes, ): MessageImageVideoItem? { val (maxWidth, maxHeight) = timelineMediaSizeProvider.getMaxSize() val thumbnailData = ImageContentRenderer.Data( - eventId = informationData.eventId, - filename = messageContent.body, - mimeType = messageContent.mimeType, - url = messageContent.videoInfo?.getThumbnailUrl(), - elementToDecrypt = messageContent.videoInfo?.thumbnailFile?.toElementToDecrypt(), - height = messageContent.videoInfo?.height, - maxHeight = maxHeight, - width = messageContent.videoInfo?.width, - maxWidth = maxWidth, - allowNonMxcUrls = informationData.sendState.isSending() + eventId = informationData.eventId, + filename = messageContent.body, + mimeType = messageContent.mimeType, + url = messageContent.videoInfo?.getThumbnailUrl(), + elementToDecrypt = messageContent.videoInfo?.thumbnailFile?.toElementToDecrypt(), + height = messageContent.videoInfo?.height, + maxHeight = maxHeight, + width = messageContent.videoInfo?.width, + maxWidth = maxWidth, + allowNonMxcUrls = informationData.sendState.isSending() ) val videoData = VideoContentRenderer.Data( - eventId = informationData.eventId, - filename = messageContent.body, - mimeType = messageContent.mimeType, - url = messageContent.getFileUrl(), - elementToDecrypt = messageContent.encryptedFileInfo?.toElementToDecrypt(), - thumbnailMediaData = thumbnailData + eventId = informationData.eventId, + filename = messageContent.body, + mimeType = messageContent.mimeType, + url = messageContent.getFileUrl(), + elementToDecrypt = messageContent.encryptedFileInfo?.toElementToDecrypt(), + thumbnailMediaData = thumbnailData ) return MessageImageVideoItem_() - .leftGuideline(avatarSizeProvider.leftGuideline) - .attributes(attributes) - .imageContentRenderer(imageContentRenderer) - .contentUploadStateTrackerBinder(contentUploadStateTrackerBinder) - .playable(true) - .highlighted(highlight) - .mediaData(thumbnailData) - .clickListener { view -> callback?.onVideoMessageClicked(messageContent, videoData, view.findViewById(R.id.messageThumbnailView)) } + .leftGuideline(avatarSizeProvider.leftGuideline) + .attributes(attributes) + .imageContentRenderer(imageContentRenderer) + .contentUploadStateTrackerBinder(contentUploadStateTrackerBinder) + .playable(true) + .highlighted(highlight) + .mediaData(thumbnailData) + .clickListener { view -> callback?.onVideoMessageClicked(messageContent, videoData, view.findViewById(R.id.messageThumbnailView)) } } private fun buildItemForTextContent( - messageContent: MessageTextContent, - informationData: MessageInformationData, - highlight: Boolean, - callback: TimelineEventController.Callback?, - attributes: AbsMessageItem.Attributes, + messageContent: MessageTextContent, + informationData: MessageInformationData, + highlight: Boolean, + callback: TimelineEventController.Callback?, + attributes: AbsMessageItem.Attributes, ): VectorEpoxyModel<*>? { val matrixFormattedBody = messageContent.matrixFormattedBody return if (matrixFormattedBody != null) { @@ -603,11 +606,11 @@ class MessageItemFactory @Inject constructor( } private fun buildFormattedTextItem( - matrixFormattedBody: String, - informationData: MessageInformationData, - highlight: Boolean, - callback: TimelineEventController.Callback?, - attributes: AbsMessageItem.Attributes, + matrixFormattedBody: String, + informationData: MessageInformationData, + highlight: Boolean, + callback: TimelineEventController.Callback?, + attributes: AbsMessageItem.Attributes, ): MessageTextItem? { val compressed = htmlCompressor.compress(matrixFormattedBody) val renderedFormattedBody = htmlRenderer.get().render(compressed, pillsPostProcessor) as Spanned @@ -615,42 +618,42 @@ class MessageItemFactory @Inject constructor( } private fun buildMessageTextItem( - body: CharSequence, - isFormatted: Boolean, - informationData: MessageInformationData, - highlight: Boolean, - callback: TimelineEventController.Callback?, - attributes: AbsMessageItem.Attributes, + body: CharSequence, + isFormatted: Boolean, + informationData: MessageInformationData, + highlight: Boolean, + callback: TimelineEventController.Callback?, + attributes: AbsMessageItem.Attributes, ): MessageTextItem? { val renderedBody = textRenderer.render(body) val bindingOptions = spanUtils.getBindingOptions(renderedBody) val linkifiedBody = renderedBody.linkify(callback) return MessageTextItem_() - .message( - if (informationData.hasBeenEdited) { - annotateWithEdited(linkifiedBody, callback, informationData) - } else { - linkifiedBody - }.toEpoxyCharSequence() - ) - .useBigFont(linkifiedBody.length <= MAX_NUMBER_OF_EMOJI_FOR_BIG_FONT * 2 && containsOnlyEmojis(linkifiedBody.toString())) - .bindingOptions(bindingOptions) - .markwonPlugins(htmlRenderer.get().plugins) - .searchForPills(isFormatted) - .previewUrlRetriever(callback?.getPreviewUrlRetriever()) - .imageContentRenderer(imageContentRenderer) - .previewUrlCallback(callback) - .leftGuideline(avatarSizeProvider.leftGuideline) - .attributes(attributes) - .highlighted(highlight) - .movementMethod(createLinkMovementMethod(callback)) + .message( + if (informationData.hasBeenEdited) { + annotateWithEdited(linkifiedBody, callback, informationData) + } else { + linkifiedBody + }.toEpoxyCharSequence() + ) + .useBigFont(linkifiedBody.length <= MAX_NUMBER_OF_EMOJI_FOR_BIG_FONT * 2 && containsOnlyEmojis(linkifiedBody.toString())) + .bindingOptions(bindingOptions) + .markwonPlugins(htmlRenderer.get().plugins) + .searchForPills(isFormatted) + .previewUrlRetriever(callback?.getPreviewUrlRetriever()) + .imageContentRenderer(imageContentRenderer) + .previewUrlCallback(callback) + .leftGuideline(avatarSizeProvider.leftGuideline) + .attributes(attributes) + .highlighted(highlight) + .movementMethod(createLinkMovementMethod(callback)) } private fun annotateWithEdited( - linkifiedBody: CharSequence, - callback: TimelineEventController.Callback?, - informationData: MessageInformationData, + linkifiedBody: CharSequence, + callback: TimelineEventController.Callback?, + informationData: MessageInformationData, ): Spannable { val spannable = SpannableStringBuilder() spannable.append(linkifiedBody) @@ -660,40 +663,44 @@ class MessageItemFactory @Inject constructor( val editStart = spannable.lastIndexOf(editedSuffix) val editEnd = editStart + editedSuffix.length spannable.setSpan( - ForegroundColorSpan(color), - editStart, - editEnd, - Spanned.SPAN_INCLUSIVE_EXCLUSIVE) + ForegroundColorSpan(color), + editStart, + editEnd, + Spanned.SPAN_INCLUSIVE_EXCLUSIVE + ) // Note: text size is set to 14sp spannable.setSpan( - AbsoluteSizeSpan(dimensionConverter.spToPx(13)), - editStart, - editEnd, - Spanned.SPAN_INCLUSIVE_EXCLUSIVE) + AbsoluteSizeSpan(dimensionConverter.spToPx(13)), + editStart, + editEnd, + Spanned.SPAN_INCLUSIVE_EXCLUSIVE + ) - spannable.setSpan(object : ClickableSpan() { - override fun onClick(widget: View) { - callback?.onEditedDecorationClicked(informationData) - } + spannable.setSpan( + object : ClickableSpan() { + override fun onClick(widget: View) { + callback?.onEditedDecorationClicked(informationData) + } - override fun updateDrawState(ds: TextPaint) { - // nop - } - }, - editStart, - editEnd, - Spanned.SPAN_INCLUSIVE_EXCLUSIVE) + override fun updateDrawState(ds: TextPaint) { + // nop + } + }, + editStart, + editEnd, + Spanned.SPAN_INCLUSIVE_EXCLUSIVE + ) return spannable } private fun buildNoticeMessageItem( - messageContent: MessageNoticeContent, - @Suppress("UNUSED_PARAMETER") - informationData: MessageInformationData, - highlight: Boolean, - callback: TimelineEventController.Callback?, - attributes: AbsMessageItem.Attributes, + messageContent: MessageNoticeContent, + @Suppress("UNUSED_PARAMETER") + informationData: MessageInformationData, + highlight: Boolean, + callback: TimelineEventController.Callback?, + attributes: AbsMessageItem.Attributes, ): MessageTextItem? { val htmlBody = messageContent.getHtmlBody() val formattedBody = span { @@ -706,23 +713,23 @@ class MessageItemFactory @Inject constructor( val message = formattedBody.linkify(callback) return MessageTextItem_() - .leftGuideline(avatarSizeProvider.leftGuideline) - .previewUrlRetriever(callback?.getPreviewUrlRetriever()) - .imageContentRenderer(imageContentRenderer) - .previewUrlCallback(callback) - .attributes(attributes) - .message(message.toEpoxyCharSequence()) - .bindingOptions(bindingOptions) - .highlighted(highlight) - .movementMethod(createLinkMovementMethod(callback)) + .leftGuideline(avatarSizeProvider.leftGuideline) + .previewUrlRetriever(callback?.getPreviewUrlRetriever()) + .imageContentRenderer(imageContentRenderer) + .previewUrlCallback(callback) + .attributes(attributes) + .message(message.toEpoxyCharSequence()) + .bindingOptions(bindingOptions) + .highlighted(highlight) + .movementMethod(createLinkMovementMethod(callback)) } private fun buildEmoteMessageItem( - messageContent: MessageEmoteContent, - informationData: MessageInformationData, - highlight: Boolean, - callback: TimelineEventController.Callback?, - attributes: AbsMessageItem.Attributes, + messageContent: MessageEmoteContent, + informationData: MessageInformationData, + highlight: Boolean, + callback: TimelineEventController.Callback?, + attributes: AbsMessageItem.Attributes, ): MessageTextItem? { val formattedBody = SpannableStringBuilder() formattedBody.append("* ${informationData.memberName} ") @@ -731,48 +738,48 @@ class MessageItemFactory @Inject constructor( val message = formattedBody.linkify(callback) return MessageTextItem_() - .message( - if (informationData.hasBeenEdited) { - annotateWithEdited(message, callback, informationData) - } else { - message - }.toEpoxyCharSequence() - ) - .bindingOptions(bindingOptions) - .leftGuideline(avatarSizeProvider.leftGuideline) - .previewUrlRetriever(callback?.getPreviewUrlRetriever()) - .imageContentRenderer(imageContentRenderer) - .previewUrlCallback(callback) - .attributes(attributes) - .highlighted(highlight) - .movementMethod(createLinkMovementMethod(callback)) + .message( + if (informationData.hasBeenEdited) { + annotateWithEdited(message, callback, informationData) + } else { + message + }.toEpoxyCharSequence() + ) + .bindingOptions(bindingOptions) + .leftGuideline(avatarSizeProvider.leftGuideline) + .previewUrlRetriever(callback?.getPreviewUrlRetriever()) + .imageContentRenderer(imageContentRenderer) + .previewUrlCallback(callback) + .attributes(attributes) + .highlighted(highlight) + .movementMethod(createLinkMovementMethod(callback)) } private fun MessageContentWithFormattedBody.getHtmlBody(): CharSequence { return matrixFormattedBody - ?.let { htmlCompressor.compress(it) } - ?.let { htmlRenderer.get().render(it, pillsPostProcessor) } + ?.let { htmlCompressor.compress(it) } + ?.let { htmlRenderer.get().render(it, pillsPostProcessor) } ?: body } private fun buildRedactedItem( - attributes: AbsMessageItem.Attributes, - highlight: Boolean, + attributes: AbsMessageItem.Attributes, + highlight: Boolean, ): RedactedMessageItem? { return RedactedMessageItem_() - .layout(attributes.informationData.messageLayout.layoutRes) - .leftGuideline(avatarSizeProvider.leftGuideline) - .attributes(attributes) - .highlighted(highlight) + .layout(attributes.informationData.messageLayout.layoutRes) + .leftGuideline(avatarSizeProvider.leftGuideline) + .attributes(attributes) + .highlighted(highlight) } private fun List?.toFft(): List? { return this - ?.filterNotNull() - ?.map { - // Value comes from AudioWaveformView.MAX_FFT, and 1024 is the max value in the Matrix spec - it * AudioWaveformView.MAX_FFT / 1024 - } + ?.filterNotNull() + ?.map { + // Value comes from AudioWaveformView.MAX_FFT, and 1024 is the max value in the Matrix spec + it * AudioWaveformView.MAX_FFT / 1024 + } } companion object { diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/TimelineFactory.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/TimelineFactory.kt index 3ec1366131..5d2f69eee4 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/TimelineFactory.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/TimelineFactory.kt @@ -22,6 +22,7 @@ import im.vector.app.features.home.room.detail.timeline.merged.MergedTimelines import kotlinx.coroutines.CoroutineScope import org.matrix.android.sdk.api.session.Session import org.matrix.android.sdk.api.session.events.model.EventType +import org.matrix.android.sdk.api.session.getRoom import org.matrix.android.sdk.api.session.room.Room import org.matrix.android.sdk.api.session.room.timeline.Timeline import javax.inject.Inject @@ -44,18 +45,18 @@ class TimelineFactory @Inject constructor(private val session: Session, private val settings = timelineSettingsFactory.create(rootThreadEventId) if (!session.vectorCallService.protocolChecker.supportVirtualRooms) { - return mainRoom.createTimeline(eventId, settings) + return mainRoom.timelineService().createTimeline(eventId, settings) } val virtualRoomId = session.vectorCallService.userMapper.virtualRoomForNativeRoom(mainRoom.roomId) return if (virtualRoomId == null) { - mainRoom.createTimeline(eventId, settings) + mainRoom.timelineService().createTimeline(eventId, settings) } else { val virtualRoom = session.getRoom(virtualRoomId)!! MergedTimelines( coroutineScope = coroutineScope, - mainTimeline = mainRoom.createTimeline(eventId, settings), + mainTimeline = mainRoom.timelineService().createTimeline(eventId, settings), secondaryTimelineParams = MergedTimelines.SecondaryTimelineParams( - timeline = virtualRoom.createTimeline(null, settings), + timeline = virtualRoom.timelineService().createTimeline(null, settings), shouldFilterTypes = true, allowedTypes = secondaryTimelineAllowedTypes ) diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/TimelineItemFactory.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/TimelineItemFactory.kt index b5d620658e..f4bcc1ba65 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/TimelineItemFactory.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/TimelineItemFactory.kt @@ -50,13 +50,15 @@ class TimelineItemFactory @Inject constructor( timelineEvent = event, highlightedEventId = params.highlightedEventId, isFromThreadTimeline = params.isFromThreadTimeline(), - rootThreadEventId = params.rootThreadEventId)) { + rootThreadEventId = params.rootThreadEventId + )) { return buildEmptyItem( event, params.prevEvent, params.highlightedEventId, params.rootThreadEventId, - params.isFromThreadTimeline()) + params.isFromThreadTimeline() + ) } // Manage state event differently, to check validity @@ -151,7 +153,8 @@ class TimelineItemFactory @Inject constructor( params.prevEvent, params.highlightedEventId, params.rootThreadEventId, - params.isFromThreadTimeline()) + params.isFromThreadTimeline() + ) } private fun buildEmptyItem(timelineEvent: TimelineEvent, @@ -163,7 +166,8 @@ class TimelineItemFactory @Inject constructor( timelineEvent = prevEvent, highlightedEventId = highlightedEventId, isFromThreadTimeline = isFromThreadTimeline, - rootThreadEventId = rootThreadEventId) + rootThreadEventId = rootThreadEventId + ) return TimelineEmptyItem_() .id(timelineEvent.localId) .eventId(timelineEvent.eventId) diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/VerificationItemFactory.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/VerificationItemFactory.kt index fb8f8fce9d..e679b8d059 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/VerificationItemFactory.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/VerificationItemFactory.kt @@ -32,14 +32,16 @@ import org.matrix.android.sdk.api.session.crypto.verification.safeValueOf import org.matrix.android.sdk.api.session.events.model.EventType import org.matrix.android.sdk.api.session.events.model.RelationType import org.matrix.android.sdk.api.session.events.model.toModel +import org.matrix.android.sdk.api.session.getRoom +import org.matrix.android.sdk.api.session.room.getTimelineEvent import org.matrix.android.sdk.api.session.room.model.message.MessageRelationContent import org.matrix.android.sdk.api.session.room.model.message.MessageVerificationCancelContent import javax.inject.Inject /** - * Can creates verification conclusion items + * Can creates verification conclusion items. * Notice that not all KEY_VERIFICATION_DONE will be displayed in timeline, - * several checks are made to see if this conclusion is attached to a known request + * several checks are made to see if this conclusion is attached to a known request. */ class VerificationItemFactory @Inject constructor( private val messageColorProvider: MessageColorProvider, diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/format/EventDetailsFormatter.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/format/EventDetailsFormatter.kt index 6296d31e48..4c92ab0e34 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/format/EventDetailsFormatter.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/format/EventDetailsFormatter.kt @@ -56,7 +56,7 @@ class EventDetailsFormatter @Inject constructor( } /** - * Example: "1024 x 720 - 670 kB" + * Example: "1024 x 720 - 670 kB". */ private fun formatForImageMessage(event: Event): CharSequence? { return event.getClearContent().toModel()?.info @@ -64,7 +64,7 @@ class EventDetailsFormatter @Inject constructor( } /** - * Example: "02:45 - 1024 x 720 - 670 kB" + * Example: "02:45 - 1024 x 720 - 670 kB". */ private fun formatForVideoMessage(event: Event): CharSequence? { return event.getClearContent().toModel()?.videoInfo @@ -72,7 +72,7 @@ class EventDetailsFormatter @Inject constructor( } /** - * Example: "02:45 - 670 kB" + * Example: "02:45 - 670 kB". */ private fun formatForAudioMessage(event: Event): CharSequence? { return event.getClearContent().toModel()?.audioInfo @@ -84,7 +84,7 @@ class EventDetailsFormatter @Inject constructor( } /** - * Example: "670 kB - application/pdf" + * Example: "670 kB - application/pdf". */ private fun formatForFileMessage(event: Event): CharSequence? { return event.getClearContent().toModel()?.info diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/format/NoticeEventFormatter.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/format/NoticeEventFormatter.kt index 51dc26247c..7ad0cb27c6 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/format/NoticeEventFormatter.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/format/NoticeEventFormatter.kt @@ -276,11 +276,15 @@ class NoticeEventFormatter @Inject constructor( val historyVisibilitySuffix = roomHistoryVisibilityFormatter.getNoticeSuffix(historyVisibility) return if (event.isSentByCurrentUser()) { - sp.getString(if (isDm) R.string.notice_made_future_direct_room_visibility_by_you else R.string.notice_made_future_room_visibility_by_you, - historyVisibilitySuffix) + sp.getString( + if (isDm) R.string.notice_made_future_direct_room_visibility_by_you else R.string.notice_made_future_room_visibility_by_you, + historyVisibilitySuffix + ) } else { - sp.getString(if (isDm) R.string.notice_made_future_direct_room_visibility else R.string.notice_made_future_room_visibility, - senderName, historyVisibilitySuffix) + sp.getString( + if (isDm) R.string.notice_made_future_direct_room_visibility else R.string.notice_made_future_room_visibility, + senderName, historyVisibilitySuffix + ) } } @@ -298,20 +302,27 @@ class NoticeEventFormatter @Inject constructor( } else { R.string.notice_room_third_party_revoked_invite_by_you }, - prevContent.displayName) + prevContent.displayName + ) } else { - sp.getString(if (isDm) R.string.notice_direct_room_third_party_revoked_invite else R.string.notice_room_third_party_revoked_invite, - senderName, prevContent.displayName) + sp.getString( + if (isDm) R.string.notice_direct_room_third_party_revoked_invite else R.string.notice_room_third_party_revoked_invite, + senderName, prevContent.displayName + ) } } content != null -> { // Invitation case if (event.isSentByCurrentUser()) { - sp.getString(if (isDm) R.string.notice_direct_room_third_party_invite_by_you else R.string.notice_room_third_party_invite_by_you, - content.displayName) + sp.getString( + if (isDm) R.string.notice_direct_room_third_party_invite_by_you else R.string.notice_room_third_party_invite_by_you, + content.displayName + ) } else { - sp.getString(if (isDm) R.string.notice_direct_room_third_party_invite else R.string.notice_room_third_party_invite, - senderName, content.displayName) + sp.getString( + if (isDm) R.string.notice_direct_room_third_party_invite else R.string.notice_room_third_party_invite, + senderName, content.displayName + ) } } else -> null @@ -416,19 +427,21 @@ class NoticeEventFormatter @Inject constructor( return buildString { // Title - append(if (prevEventContent == null) { - if (event.isSentByCurrentUser()) { - sp.getString(R.string.notice_room_server_acl_set_title_by_you) - } else { - sp.getString(R.string.notice_room_server_acl_set_title, senderName) - } - } else { - if (event.isSentByCurrentUser()) { - sp.getString(R.string.notice_room_server_acl_updated_title_by_you) - } else { - sp.getString(R.string.notice_room_server_acl_updated_title, senderName) - } - }) + append( + if (prevEventContent == null) { + if (event.isSentByCurrentUser()) { + sp.getString(R.string.notice_room_server_acl_set_title_by_you) + } else { + sp.getString(R.string.notice_room_server_acl_set_title, senderName) + } + } else { + if (event.isSentByCurrentUser()) { + sp.getString(R.string.notice_room_server_acl_updated_title_by_you) + } else { + sp.getString(R.string.notice_room_server_acl_updated_title, senderName) + } + } + ) if (eventContent.allowList.isEmpty()) { // Special case for stuck room appendNl(sp.getString(R.string.notice_room_server_acl_allow_is_empty)) @@ -562,8 +575,10 @@ class NoticeEventFormatter @Inject constructor( if (isDm) R.string.notice_direct_room_guest_access_can_join_by_you else R.string.notice_room_guest_access_can_join_by_you ) } else { - sp.getString(if (isDm) R.string.notice_direct_room_guest_access_can_join else R.string.notice_room_guest_access_can_join, - senderName) + sp.getString( + if (isDm) R.string.notice_direct_room_guest_access_can_join else R.string.notice_room_guest_access_can_join, + senderName + ) } GuestAccess.Forbidden -> if (event.isSentByCurrentUser()) { @@ -571,8 +586,10 @@ class NoticeEventFormatter @Inject constructor( if (isDm) R.string.notice_direct_room_guest_access_forbidden_by_you else R.string.notice_room_guest_access_forbidden_by_you ) } else { - sp.getString(if (isDm) R.string.notice_direct_room_guest_access_forbidden else R.string.notice_room_guest_access_forbidden, - senderName) + sp.getString( + if (isDm) R.string.notice_direct_room_guest_access_forbidden else R.string.notice_room_guest_access_forbidden, + senderName + ) } else -> null } @@ -705,18 +722,24 @@ class NoticeEventFormatter @Inject constructor( Membership.JOIN -> eventContent.safeReason?.let { reason -> if (event.isSentByCurrentUser()) { - sp.getString(if (isDm) R.string.notice_direct_room_join_with_reason_by_you else R.string.notice_room_join_with_reason_by_you, - reason) + sp.getString( + if (isDm) R.string.notice_direct_room_join_with_reason_by_you else R.string.notice_room_join_with_reason_by_you, + reason + ) } else { - sp.getString(if (isDm) R.string.notice_direct_room_join_with_reason else R.string.notice_room_join_with_reason, - senderDisplayName, reason) + sp.getString( + if (isDm) R.string.notice_direct_room_join_with_reason else R.string.notice_room_join_with_reason, + senderDisplayName, reason + ) } } ?: run { if (event.isSentByCurrentUser()) { sp.getString(if (isDm) R.string.notice_direct_room_join_by_you else R.string.notice_room_join_by_you) } else { - sp.getString(if (isDm) R.string.notice_direct_room_join else R.string.notice_room_join, - senderDisplayName) + sp.getString( + if (isDm) R.string.notice_direct_room_join else R.string.notice_room_join, + senderDisplayName + ) } } Membership.LEAVE -> @@ -745,15 +768,19 @@ class NoticeEventFormatter @Inject constructor( reason ) } else { - sp.getString(if (isDm) R.string.notice_direct_room_leave_with_reason else R.string.notice_room_leave_with_reason, - senderDisplayName, reason) + sp.getString( + if (isDm) R.string.notice_direct_room_leave_with_reason else R.string.notice_room_leave_with_reason, + senderDisplayName, reason + ) } } ?: run { if (event.isSentByCurrentUser()) { sp.getString(if (isDm) R.string.notice_direct_room_leave_by_you else R.string.notice_room_leave_by_you) } else { - sp.getString(if (isDm) R.string.notice_direct_room_leave else R.string.notice_room_leave, - senderDisplayName) + sp.getString( + if (isDm) R.string.notice_direct_room_leave else R.string.notice_room_leave, + senderDisplayName + ) } } } @@ -824,8 +851,10 @@ class NoticeEventFormatter @Inject constructor( if (event.isSentByCurrentUser()) { sp.getString(if (isDm) R.string.direct_room_join_rules_invite_by_you else R.string.room_join_rules_invite_by_you) } else { - sp.getString(if (isDm) R.string.direct_room_join_rules_invite else R.string.room_join_rules_invite, - senderName) + sp.getString( + if (isDm) R.string.direct_room_join_rules_invite else R.string.room_join_rules_invite, + senderName + ) } RoomJoinRules.PUBLIC -> if (event.isSentByCurrentUser()) { diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/format/RoomHistoryVisibilityFormatter.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/format/RoomHistoryVisibilityFormatter.kt index 14769bc95b..c1ba085fd7 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/format/RoomHistoryVisibilityFormatter.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/format/RoomHistoryVisibilityFormatter.kt @@ -25,20 +25,24 @@ class RoomHistoryVisibilityFormatter @Inject constructor( private val stringProvider: StringProvider ) { fun getNoticeSuffix(roomHistoryVisibility: RoomHistoryVisibility): String { - return stringProvider.getString(when (roomHistoryVisibility) { - RoomHistoryVisibility.WORLD_READABLE -> R.string.notice_room_visibility_world_readable - RoomHistoryVisibility.SHARED -> R.string.notice_room_visibility_shared - RoomHistoryVisibility.INVITED -> R.string.notice_room_visibility_invited - RoomHistoryVisibility.JOINED -> R.string.notice_room_visibility_joined - }) + return stringProvider.getString( + when (roomHistoryVisibility) { + RoomHistoryVisibility.WORLD_READABLE -> R.string.notice_room_visibility_world_readable + RoomHistoryVisibility.SHARED -> R.string.notice_room_visibility_shared + RoomHistoryVisibility.INVITED -> R.string.notice_room_visibility_invited + RoomHistoryVisibility.JOINED -> R.string.notice_room_visibility_joined + } + ) } fun getSetting(roomHistoryVisibility: RoomHistoryVisibility): String { - return stringProvider.getString(when (roomHistoryVisibility) { - RoomHistoryVisibility.WORLD_READABLE -> R.string.room_settings_read_history_entry_anyone - RoomHistoryVisibility.SHARED -> R.string.room_settings_read_history_entry_members_only_option_time_shared - RoomHistoryVisibility.INVITED -> R.string.room_settings_read_history_entry_members_only_invited - RoomHistoryVisibility.JOINED -> R.string.room_settings_read_history_entry_members_only_joined - }) + return stringProvider.getString( + when (roomHistoryVisibility) { + RoomHistoryVisibility.WORLD_READABLE -> R.string.room_settings_read_history_entry_anyone + RoomHistoryVisibility.SHARED -> R.string.room_settings_read_history_entry_members_only_option_time_shared + RoomHistoryVisibility.INVITED -> R.string.room_settings_read_history_entry_members_only_invited + RoomHistoryVisibility.JOINED -> R.string.room_settings_read_history_entry_members_only_joined + } + ) } } diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/helper/AudioMessagePlaybackTracker.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/helper/AudioMessagePlaybackTracker.kt index 0312ac9e6f..44387759c8 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/helper/AudioMessagePlaybackTracker.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/helper/AudioMessagePlaybackTracker.kt @@ -63,7 +63,7 @@ class AudioMessagePlaybackTracker @Inject constructor() { } /** - * Set state and notify the listeners + * Set state and notify the listeners. */ private fun setState(key: String, state: Listener.State) { states[key] = state diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/helper/ContentUploadStateTrackerBinder.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/helper/ContentUploadStateTrackerBinder.kt index 9ff8ddfbce..b8882b3f47 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/helper/ContentUploadStateTrackerBinder.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/helper/ContentUploadStateTrackerBinder.kt @@ -156,9 +156,11 @@ private class ContentMediaProgressUpdater(private val progressLayout: ViewGroup, progressBar.isIndeterminate = false progressBar.progress = percent.toInt() progressTextView.isVisible = true - progressTextView.text = progressLayout.context.getString(resId, + progressTextView.text = progressLayout.context.getString( + resId, TextUtils.formatFileSize(progressLayout.context, current, true), - TextUtils.formatFileSize(progressLayout.context, total, true)) + TextUtils.formatFileSize(progressLayout.context, total, true) + ) progressTextView.setTextColor(messageColorProvider.getMessageTextColor(SendState.SENDING)) } diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/helper/LocationPinProvider.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/helper/LocationPinProvider.kt index 7262284c95..8ef910c931 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/helper/LocationPinProvider.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/helper/LocationPinProvider.kt @@ -29,6 +29,7 @@ import im.vector.app.core.di.ActiveSessionHolder import im.vector.app.core.glide.GlideApp import im.vector.app.core.utils.DimensionConverter import im.vector.app.features.home.AvatarRenderer +import org.matrix.android.sdk.api.session.getUser import org.matrix.android.sdk.api.util.toMatrixItem import timber.log.Timber import javax.inject.Inject diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/helper/MessageInformationDataFactory.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/helper/MessageInformationDataFactory.kt index f882840eee..1e95f067d2 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/helper/MessageInformationDataFactory.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/helper/MessageInformationDataFactory.kt @@ -44,8 +44,8 @@ import org.matrix.android.sdk.api.session.room.timeline.hasBeenEdited import javax.inject.Inject /** + * This class compute if data of an event (such has avatar, display name, ...) should be displayed, depending on the previous event in the timeline. * TODO Update this comment - * This class compute if data of an event (such has avatar, display name, ...) should be displayed, depending on the previous event in the timeline */ class MessageInformationDataFactory @Inject constructor(private val session: Session, private val dateFormatter: VectorDateFormatter, @@ -190,7 +190,7 @@ class MessageInformationDataFactory @Inject constructor(private val session: Ses /** * Tiles type message never show the sender information (like verification request), so we should repeat it for next message - * even if same sender + * even if same sender. */ private fun isTileTypeMessage(event: TimelineEvent?): Boolean { return when (event?.root?.getClearType()) { diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/helper/TimelineControllerInterceptorHelper.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/helper/TimelineControllerInterceptorHelper.kt index 8a0e1e18fd..e9361e564c 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/helper/TimelineControllerInterceptorHelper.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/helper/TimelineControllerInterceptorHelper.kt @@ -25,6 +25,7 @@ import im.vector.app.features.home.room.detail.timeline.item.DaySeparatorItem import im.vector.app.features.home.room.detail.timeline.item.ItemWithEvents import im.vector.app.features.home.room.detail.timeline.item.TimelineReadMarkerItem_ import org.matrix.android.sdk.api.session.room.timeline.Timeline +import kotlin.random.Random import kotlin.reflect.KMutableProperty0 private const val DEFAULT_PREFETCH_THRESHOLD = 30 @@ -104,7 +105,7 @@ class TimelineControllerInterceptorHelper(private val positionOfReadMarker: KMut .coerceAtLeast(0) val loadingItem = LoadingItem_() - .id("prefetch_backward_loading${System.currentTimeMillis()}") + .id("prefetch_backward_loading${Random.nextLong()}") .showLoader(false) .setVisibilityStateChangedListener(Timeline.Direction.BACKWARDS, callback) @@ -120,7 +121,7 @@ class TimelineControllerInterceptorHelper(private val positionOfReadMarker: KMut .coerceAtLeast(0) val loadingItem = LoadingItem_() - .id("prefetch_forward_loading${System.currentTimeMillis()}") + .id("prefetch_forward_loading${Random.nextLong()}") .showLoader(false) .setVisibilityStateChangedListener(Timeline.Direction.FORWARDS, callback) add(indexOfPrefetchForward, loadingItem) diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/helper/TimelineSettingsFactory.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/helper/TimelineSettingsFactory.kt index 8b7dcc9c72..18bdc2fdaa 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/helper/TimelineSettingsFactory.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/helper/TimelineSettingsFactory.kt @@ -26,7 +26,8 @@ class TimelineSettingsFactory @Inject constructor(private val userPreferencesPro return TimelineSettings( initialSize = 30, buildReadReceipts = userPreferencesProvider.shouldShowReadReceipts(), - rootThreadEventId = rootThreadEventId + rootThreadEventId = rootThreadEventId, + useLiveSenderInfo = userPreferencesProvider.showLiveSenderInfo() ) } } diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/helper/TimelineVisibilityStateChangedListeners.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/helper/TimelineVisibilityStateChangedListeners.kt index 2337a6ea15..95feef83c0 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/helper/TimelineVisibilityStateChangedListeners.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/helper/TimelineVisibilityStateChangedListeners.kt @@ -22,7 +22,7 @@ import im.vector.app.features.home.room.detail.timeline.TimelineEventController import org.matrix.android.sdk.api.session.room.timeline.TimelineEvent class ReadMarkerVisibilityStateChangedListener(private val callback: TimelineEventController.Callback?) : - VectorEpoxyModel.OnVisibilityStateChangedListener { + VectorEpoxyModel.OnVisibilityStateChangedListener { override fun onVisibilityStateChanged(visibilityState: Int) { if (visibilityState == VisibilityState.VISIBLE) { @@ -33,7 +33,7 @@ class ReadMarkerVisibilityStateChangedListener(private val callback: TimelineEve class TimelineEventVisibilityStateChangedListener(private val callback: TimelineEventController.Callback?, private val event: TimelineEvent) : - VectorEpoxyModel.OnVisibilityStateChangedListener { + VectorEpoxyModel.OnVisibilityStateChangedListener { override fun onVisibilityStateChanged(visibilityState: Int) { if (visibilityState == VisibilityState.VISIBLE) { @@ -46,7 +46,7 @@ class TimelineEventVisibilityStateChangedListener(private val callback: Timeline class MergedTimelineEventVisibilityStateChangedListener(private val callback: TimelineEventController.Callback?, private val events: List) : - VectorEpoxyModel.OnVisibilityStateChangedListener { + VectorEpoxyModel.OnVisibilityStateChangedListener { override fun onVisibilityStateChanged(visibilityState: Int) { if (visibilityState == VisibilityState.VISIBLE) { diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/AbsMessageItem.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/AbsMessageItem.kt index 30c366738d..263f105b6b 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/AbsMessageItem.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/AbsMessageItem.kt @@ -39,8 +39,8 @@ import org.matrix.android.sdk.api.session.threads.ThreadDetails import org.matrix.android.sdk.api.util.MatrixItem /** - * Base timeline item that adds an optional information bar with the sender avatar, name, time, send state - * Adds associated click listeners (on avatar, displayname) + * Base timeline item that adds an optional information bar with the sender avatar, name, time, send state. + * Adds associated click listeners (on avatar, displayname). */ abstract class AbsMessageItem : AbsBaseMessageItem() { diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/BaseEventItem.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/BaseEventItem.kt index 8ea761830a..b72f492c8c 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/BaseEventItem.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/BaseEventItem.kt @@ -28,7 +28,7 @@ import im.vector.app.core.epoxy.VectorEpoxyModel import im.vector.app.core.platform.CheckableView /** - * Children must override getViewType() + * Children must override getViewType(). */ abstract class BaseEventItem : VectorEpoxyModel(), ItemWithEvents { diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/CallTileTimelineItem.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/CallTileTimelineItem.kt index ea130901b1..b56f5264e6 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/CallTileTimelineItem.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/CallTileTimelineItem.kt @@ -209,13 +209,13 @@ abstract class CallTileTimelineItem : AbsBaseMessageItem { holder.statusView.setStatus(R.string.call_tile_video_active) } - attributes.informationData.sentByMe -> { + attributes.informationData.sentByMe -> { holder.statusView.setStatus(R.string.call_ringing) } - attributes.callKind.isVoiceCall -> { + attributes.callKind.isVoiceCall -> { holder.statusView.setStatus(R.string.call_tile_voice_incoming) } - else -> { + else -> { holder.statusView.setStatus(R.string.call_tile_video_incoming) } } diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/ItemWithEvents.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/ItemWithEvents.kt index d508023668..8730c1a00d 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/ItemWithEvents.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/ItemWithEvents.kt @@ -28,7 +28,7 @@ interface ItemWithEvents { fun isVisible(): Boolean = true /** - * Returns false if you want epoxy controller to rebuild the event each time a built is triggered + * Returns false if you want epoxy controller to rebuild the event each time a built is triggered. */ fun isCacheable(): Boolean = true } diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageAudioItem.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageAudioItem.kt index 3c071578cc..f574dcfdcb 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageAudioItem.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageAudioItem.kt @@ -127,9 +127,11 @@ abstract class MessageAudioItem : AbsMessageItem() { (duration * (progress.toFloat() / 100)).toInt() ) } + override fun onStartTrackingTouch(seekBar: SeekBar) { isUserSeeking = true } + override fun onStopTrackingTouch(seekBar: SeekBar) { isUserSeeking = false val percentage = seekBar.progress.toFloat() / 100 diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageVoiceItem.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageVoiceItem.kt index 02937574f2..82860da886 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageVoiceItem.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageVoiceItem.kt @@ -126,9 +126,9 @@ abstract class MessageVoiceItem : AbsMessageItem() { audioMessagePlaybackTracker.track(attributes.informationData.eventId, object : AudioMessagePlaybackTracker.Listener { override fun onUpdate(state: AudioMessagePlaybackTracker.Listener.State) { when (state) { - is AudioMessagePlaybackTracker.Listener.State.Idle -> renderIdleState(holder, waveformColorIdle, waveformColorPlayed) - is AudioMessagePlaybackTracker.Listener.State.Playing -> renderPlayingState(holder, state, waveformColorIdle, waveformColorPlayed) - is AudioMessagePlaybackTracker.Listener.State.Paused -> renderPausedState(holder, state, waveformColorIdle, waveformColorPlayed) + is AudioMessagePlaybackTracker.Listener.State.Idle -> renderIdleState(holder, waveformColorIdle, waveformColorPlayed) + is AudioMessagePlaybackTracker.Listener.State.Playing -> renderPlayingState(holder, state, waveformColorIdle, waveformColorPlayed) + is AudioMessagePlaybackTracker.Listener.State.Paused -> renderPausedState(holder, state, waveformColorIdle, waveformColorPlayed) is AudioMessagePlaybackTracker.Listener.State.Recording -> Unit } } diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/PollOptionViewState.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/PollOptionViewState.kt index ae900d0406..fe17f9e9e7 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/PollOptionViewState.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/PollOptionViewState.kt @@ -26,7 +26,7 @@ sealed class PollOptionViewState(open val optionId: String, ) : PollOptionViewState(optionId, optionAnswer) /** - * Represents a poll that is sent but not voted by the user + * Represents a poll that is sent but not voted by the user. */ data class PollReady(override val optionId: String, override val optionAnswer: String diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/StatusTileTimelineItem.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/StatusTileTimelineItem.kt index 2d9119f14c..3810f1cb6f 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/StatusTileTimelineItem.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/StatusTileTimelineItem.kt @@ -57,7 +57,7 @@ abstract class StatusTileTimelineItem : AbsBaseMessageItem R.drawable.ic_shield_trusted ShieldUIState.BLACK -> R.drawable.ic_shield_black ShieldUIState.RED -> R.drawable.ic_shield_warning - ShieldUIState.ERROR -> R.drawable.ic_warning_badge + ShieldUIState.ERROR -> R.drawable.ic_warning_badge } holder.titleView.setCompoundDrawablesWithIntrinsicBounds( diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/VerificationRequestItem.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/VerificationRequestItem.kt index 0e6530fdca..fc4c55d1f3 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/VerificationRequestItem.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/VerificationRequestItem.kt @@ -31,6 +31,7 @@ import com.airbnb.epoxy.EpoxyModelClass import im.vector.app.R import im.vector.app.core.epoxy.ClickListener import im.vector.app.core.epoxy.onClick +import im.vector.app.core.time.Clock import im.vector.app.features.home.AvatarRenderer import im.vector.app.features.home.room.detail.RoomDetailAction import im.vector.app.features.home.room.detail.timeline.MessageColorProvider @@ -47,6 +48,9 @@ abstract class VerificationRequestItem : AbsBaseMessageItem() { diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/reactions/ViewReactionsBottomSheet.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/reactions/ViewReactionsBottomSheet.kt index 6ae3cd227f..6c6a722f02 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/reactions/ViewReactionsBottomSheet.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/reactions/ViewReactionsBottomSheet.kt @@ -35,7 +35,7 @@ import im.vector.app.features.home.room.detail.timeline.item.MessageInformationD import javax.inject.Inject /** - * Bottom sheet displaying list of reactions for a given event ordered by timestamp + * Bottom sheet displaying list of reactions for a given event ordered by timestamp. */ @AndroidEntryPoint class ViewReactionsBottomSheet : @@ -82,11 +82,13 @@ class ViewReactionsBottomSheet : companion object { fun newInstance(roomId: String, informationData: MessageInformationData): ViewReactionsBottomSheet { return ViewReactionsBottomSheet().apply { - setArguments(TimelineEventFragmentArgs( - eventId = informationData.eventId, - roomId = roomId, - informationData = informationData - )) + setArguments( + TimelineEventFragmentArgs( + eventId = informationData.eventId, + roomId = roomId, + informationData = informationData + ) + ) } } } diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/reactions/ViewReactionsEpoxyController.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/reactions/ViewReactionsEpoxyController.kt index 86e5c529ee..a4677da1d9 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/reactions/ViewReactionsEpoxyController.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/reactions/ViewReactionsEpoxyController.kt @@ -30,7 +30,7 @@ import im.vector.lib.core.utils.epoxy.charsequence.toEpoxyCharSequence import javax.inject.Inject /** - * Epoxy controller for reaction event list + * Epoxy controller for reaction event list. */ class ViewReactionsEpoxyController @Inject constructor( private val stringProvider: StringProvider, diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/reactions/ViewReactionsViewModel.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/reactions/ViewReactionsViewModel.kt index 29b8c207df..1c9127aa1e 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/reactions/ViewReactionsViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/reactions/ViewReactionsViewModel.kt @@ -33,6 +33,8 @@ import im.vector.app.core.platform.VectorViewModel import im.vector.app.features.home.room.detail.timeline.action.TimelineEventFragmentArgs import kotlinx.coroutines.flow.map import org.matrix.android.sdk.api.session.Session +import org.matrix.android.sdk.api.session.getRoom +import org.matrix.android.sdk.api.session.room.getTimelineEvent import org.matrix.android.sdk.flow.flow import org.matrix.android.sdk.flow.unwrap @@ -54,7 +56,7 @@ data class ReactionInfo( ) /** - * Used to display the list of members that reacted to a given event + * Used to display the list of members that reacted to a given event. */ class ViewReactionsViewModel @AssistedInject constructor( @Assisted initialState: DisplayReactionsViewState, diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/render/EventTextRenderer.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/render/EventTextRenderer.kt index d50a6fb297..83cffc4279 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/render/EventTextRenderer.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/render/EventTextRenderer.kt @@ -62,7 +62,7 @@ class EventTextRenderer @AssistedInject constructor(@Assisted private val roomId * ========================================================================================== */ private fun addNotifyEveryoneSpans(text: Spannable, roomId: String) { - val room: RoomSummary? = sessionHolder.getSafeActiveSession()?.getRoomSummary(roomId) + val room: RoomSummary? = sessionHolder.getSafeActiveSession()?.roomService()?.getRoomSummary(roomId) val matrixItem = MatrixItem.EveryoneInRoomItem( id = roomId, avatarUrl = room?.avatarUrl, diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/style/TimelineMessageLayout.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/style/TimelineMessageLayout.kt index ae9b004f6c..a41732be2a 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/style/TimelineMessageLayout.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/style/TimelineMessageLayout.kt @@ -29,11 +29,11 @@ sealed interface TimelineMessageLayout : Parcelable { @Parcelize data class Default( - override val showAvatar: Boolean, - override val showDisplayName: Boolean, - override val showTimestamp: Boolean, - // Keep defaultLayout generated on epoxy items - override val layoutRes: Int = 0, + override val showAvatar: Boolean, + override val showDisplayName: Boolean, + override val showTimestamp: Boolean, + // Keep defaultLayout generated on epoxy items + override val layoutRes: Int = 0, ) : TimelineMessageLayout @Parcelize @@ -56,10 +56,10 @@ sealed interface TimelineMessageLayout : Parcelable { @Parcelize data class CornersRadius( - val topStartRadius: Float, - val topEndRadius: Float, - val bottomStartRadius: Float, - val bottomEndRadius: Float, + val topStartRadius: Float, + val topEndRadius: Float, + val bottomStartRadius: Float, + val bottomEndRadius: Float, ) : Parcelable } } diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/style/TimelineMessageLayoutFactory.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/style/TimelineMessageLayoutFactory.kt index f2334e5a4f..cb0b2384ec 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/style/TimelineMessageLayoutFactory.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/style/TimelineMessageLayoutFactory.kt @@ -59,12 +59,12 @@ class TimelineMessageLayoutFactory @Inject constructor(private val session: Sess MessageType.MSGTYPE_VIDEO, MessageType.MSGTYPE_STICKER_LOCAL, MessageType.MSGTYPE_EMOTE, - MessageType.MSGTYPE_LIVE_LOCATION_STATE, + MessageType.MSGTYPE_BEACON_INFO, ) private val MSG_TYPES_WITH_TIMESTAMP_INSIDE_MESSAGE = setOf( MessageType.MSGTYPE_IMAGE, MessageType.MSGTYPE_VIDEO, - MessageType.MSGTYPE_LIVE_LOCATION_STATE, + MessageType.MSGTYPE_BEACON_INFO, ) } @@ -95,6 +95,7 @@ class TimelineMessageLayoutFactory @Inject constructor(private val session: Sess nextDisplayableEvent.root.getClearType() !in listOf(EventType.MESSAGE, EventType.STICKER, EventType.ENCRYPTED) || isNextMessageReceivedMoreThanOneHourAgo || isTileTypeMessage(nextDisplayableEvent) || + nextDisplayableEvent.isRootThread() || event.isRootThread() || nextDisplayableEvent.isEdition() @@ -151,9 +152,9 @@ class TimelineMessageLayoutFactory @Inject constructor(private val session: Sess private fun MessageContent?.shouldAddMessageOverlay(): Boolean { return when { - this == null || msgType == MessageType.MSGTYPE_LIVE_LOCATION_STATE -> false - msgType == MessageType.MSGTYPE_LOCATION -> vectorPreferences.labsRenderLocationsInTimeline() - else -> msgType in MSG_TYPES_WITH_TIMESTAMP_INSIDE_MESSAGE + this == null || msgType == MessageType.MSGTYPE_BEACON_INFO -> false + msgType == MessageType.MSGTYPE_LOCATION -> vectorPreferences.labsRenderLocationsInTimeline() + else -> msgType in MSG_TYPES_WITH_TIMESTAMP_INSIDE_MESSAGE } } @@ -196,7 +197,7 @@ class TimelineMessageLayoutFactory @Inject constructor(private val session: Sess /** * Tiles type message never show the sender information (like verification request), so we should repeat it for next message - * even if same sender + * even if same sender. */ private fun isTileTypeMessage(event: TimelineEvent?): Boolean { return when (event?.root?.getClearType()) { diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/url/PreviewUrlUiState.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/url/PreviewUrlUiState.kt index a8f8f7b0cb..29a314d1b3 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/url/PreviewUrlUiState.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/url/PreviewUrlUiState.kt @@ -19,7 +19,7 @@ package im.vector.app.features.home.room.detail.timeline.url import org.matrix.android.sdk.api.session.media.PreviewUrlData /** - * The state representing a preview url UI state for an Event + * The state representing a preview url UI state for an Event. */ sealed class PreviewUrlUiState { // No info diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/url/PreviewUrlView.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/url/PreviewUrlView.kt index bb306c2016..3ae6a1fea5 100755 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/url/PreviewUrlView.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/url/PreviewUrlView.kt @@ -35,7 +35,7 @@ import im.vector.app.features.themes.ThemeUtils import org.matrix.android.sdk.api.session.media.PreviewUrlData /** - * A View to display a PreviewUrl and some other state + * A View to display a PreviewUrl and some other state. */ class PreviewUrlView @JvmOverloads constructor( context: Context, @@ -56,7 +56,7 @@ class PreviewUrlView @JvmOverloads constructor( private var state: PreviewUrlUiState = PreviewUrlUiState.Unknown /** - * This methods is responsible for rendering the view according to the newState + * This methods is responsible for rendering the view according to the newState. * * @param newState the newState representing the view */ @@ -157,7 +157,7 @@ class PreviewUrlView @JvmOverloads constructor( } /** - * Hide all views that are not visible in all state + * Hide all views that are not visible in all state. */ private fun hideAll() { views.urlPreviewTitle.isVisible = false diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/view/MessageBubbleView.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/view/MessageBubbleView.kt index 87ed9243a5..c9665a9125 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/view/MessageBubbleView.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/view/MessageBubbleView.kt @@ -91,7 +91,8 @@ class MessageBubbleView @JvmOverloads constructor( background = RippleDrawable( ContextCompat.getColorStateList(context, R.color.mtrl_btn_ripple_color) ?: ColorStateList.valueOf(Color.TRANSPARENT), bubbleDrawable, - rippleMaskDrawable) + rippleMaskDrawable + ) } } diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/upgrade/MigrateRoomViewModel.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/upgrade/MigrateRoomViewModel.kt index 98be65c167..50eecb90fb 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/upgrade/MigrateRoomViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/upgrade/MigrateRoomViewModel.kt @@ -29,19 +29,21 @@ import im.vector.app.core.platform.VectorViewModel import im.vector.app.features.session.coroutineScope import kotlinx.coroutines.launch import org.matrix.android.sdk.api.session.Session +import org.matrix.android.sdk.api.session.getRoom +import org.matrix.android.sdk.api.session.getRoomSummary class MigrateRoomViewModel @AssistedInject constructor( @Assisted initialState: MigrateRoomViewState, private val session: Session, private val upgradeRoomViewModelTask: UpgradeRoomViewModelTask) : - VectorViewModel(initialState) { + VectorViewModel(initialState) { init { val room = session.getRoom(initialState.roomId) val summary = session.getRoomSummary(initialState.roomId) setState { copy( - currentVersion = room?.getRoomVersion(), + currentVersion = room?.roomVersionService()?.getRoomVersion(), isPublic = summary?.isPublic ?: false, otherMemberCount = summary?.otherMemberIds?.count() ?: 0, knownParents = summary?.flattenParentIds ?: emptyList() @@ -58,7 +60,7 @@ class MigrateRoomViewModel @AssistedInject constructor( override fun handle(action: MigrateRoomAction) { when (action) { - is MigrateRoomAction.SetAutoInvite -> { + is MigrateRoomAction.SetAutoInvite -> { setState { copy(shouldIssueInvites = action.autoInvite) } @@ -68,7 +70,7 @@ class MigrateRoomViewModel @AssistedInject constructor( copy(shouldUpdateKnownParents = action.update) } } - MigrateRoomAction.UpgradeRoom -> { + MigrateRoomAction.UpgradeRoom -> { handleUpgradeRoom() } } diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/upgrade/UpgradeRoomViewModelTask.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/upgrade/UpgradeRoomViewModelTask.kt index 8859aaeacf..2f91b9a35a 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/upgrade/UpgradeRoomViewModelTask.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/upgrade/UpgradeRoomViewModelTask.kt @@ -20,6 +20,7 @@ import im.vector.app.core.platform.ViewModelTask import im.vector.app.core.resources.StringProvider import org.matrix.android.sdk.api.extensions.tryOrNull import org.matrix.android.sdk.api.session.Session +import org.matrix.android.sdk.api.session.getRoom import timber.log.Timber import javax.inject.Inject @@ -49,12 +50,12 @@ class UpgradeRoomViewModelTask @Inject constructor( val room = session.getRoom(params.roomId) ?: return Result.UnknownRoom - if (!room.userMayUpgradeRoom(session.myUserId)) { + if (!room.roomVersionService().userMayUpgradeRoom(session.myUserId)) { return Result.NotAllowed } val updatedRoomId = try { - room.upgradeToVersion(params.newVersion) + room.roomVersionService().upgradeToVersion(params.newVersion) } catch (failure: Throwable) { return Result.ErrorFailure(failure) } @@ -64,7 +65,7 @@ class UpgradeRoomViewModelTask @Inject constructor( params.userIdsToAutoInvite.forEach { params.progressReporter?.invoke(false, currentStep, totalStep) tryOrNull { - session.getRoom(updatedRoomId)?.invite(it) + session.getRoom(updatedRoomId)?.membershipService()?.invite(it) } currentStep++ } diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/widget/RoomWidgetsBottomSheet.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/widget/RoomWidgetsBottomSheet.kt index 65f3d16ad4..72f9688ad8 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/widget/RoomWidgetsBottomSheet.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/widget/RoomWidgetsBottomSheet.kt @@ -37,7 +37,7 @@ import org.matrix.android.sdk.api.session.widgets.model.Widget import javax.inject.Inject /** - * Bottom sheet displaying active widgets in a room + * Bottom sheet displaying active widgets in a room. */ @AndroidEntryPoint class RoomWidgetsBottomSheet : diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/widget/RoomWidgetsController.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/widget/RoomWidgetsController.kt index b2da3bfc78..0f91f7b7c6 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/widget/RoomWidgetsController.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/widget/RoomWidgetsController.kt @@ -27,12 +27,12 @@ import org.matrix.android.sdk.api.session.widgets.model.Widget import javax.inject.Inject /** - * Epoxy controller for room widgets list + * Epoxy controller for room widgets list. */ class RoomWidgetsController @Inject constructor( val stringProvider: StringProvider, val colorProvider: ColorProvider) : - TypedEpoxyController>() { + TypedEpoxyController>() { var listener: Listener? = null diff --git a/vector/src/main/java/im/vector/app/features/home/room/list/CollapsableTypedEpoxyController.kt b/vector/src/main/java/im/vector/app/features/home/room/list/CollapsableTypedEpoxyController.kt index 15b3602766..8cf7e6bcab 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/list/CollapsableTypedEpoxyController.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/list/CollapsableTypedEpoxyController.kt @@ -19,7 +19,7 @@ package im.vector.app.features.home.room.list import com.airbnb.epoxy.EpoxyController abstract class CollapsableTypedEpoxyController : - EpoxyController(), CollapsableControllerExtension { + EpoxyController(), CollapsableControllerExtension { private var currentData: T? = null private var allowModelBuildRequests = false diff --git a/vector/src/main/java/im/vector/app/features/home/room/list/RoomListFragment.kt b/vector/src/main/java/im/vector/app/features/home/room/list/RoomListFragment.kt index aaa469f68d..2be6130a5d 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/list/RoomListFragment.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/list/RoomListFragment.kt @@ -44,12 +44,14 @@ import im.vector.app.core.platform.VectorBaseFragment import im.vector.app.core.resources.UserPreferencesProvider import im.vector.app.databinding.FragmentRoomListBinding import im.vector.app.features.analytics.plan.MobileScreen +import im.vector.app.features.analytics.plan.ViewRoom import im.vector.app.features.home.RoomListDisplayMode import im.vector.app.features.home.room.filtered.FilteredRoomFooterItem 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.widget.NotifsFabMenuView +import im.vector.app.features.matrixto.OriginOfMatrixTo import im.vector.app.features.notifications.NotificationDrawerManager import kotlinx.coroutines.flow.filter import kotlinx.coroutines.flow.launchIn @@ -179,7 +181,7 @@ class RoomListFragment @Inject constructor( } private fun handleShowMxToLink(link: String) { - navigator.openMatrixToBottomSheet(requireContext(), link) + navigator.openMatrixToBottomSheet(requireContext(), link, OriginOfMatrixTo.ROOM_LIST) } override fun onDestroyView() { @@ -196,7 +198,12 @@ class RoomListFragment @Inject constructor( } private fun handleSelectRoom(event: RoomListViewEvents.SelectRoom, isInviteAlreadyAccepted: Boolean) { - navigator.openRoom(context = requireActivity(), roomId = event.roomSummary.roomId, isInviteAlreadyAccepted = isInviteAlreadyAccepted) + navigator.openRoom( + context = requireActivity(), + roomId = event.roomSummary.roomId, + isInviteAlreadyAccepted = isInviteAlreadyAccepted, + trigger = ViewRoom.Trigger.RoomList + ) } private fun setupCreateRoomButton() { @@ -474,7 +481,8 @@ class RoomListFragment @Inject constructor( StateView.State.Empty( title = getString(R.string.room_list_catchup_empty_title), image = ContextCompat.getDrawable(requireContext(), R.drawable.ic_noun_party_popper), - message = getString(R.string.room_list_catchup_empty_body)) + message = getString(R.string.room_list_catchup_empty_body) + ) } RoomListDisplayMode.PEOPLE -> StateView.State.Empty( diff --git a/vector/src/main/java/im/vector/app/features/home/room/list/RoomListSectionBuilderGroup.kt b/vector/src/main/java/im/vector/app/features/home/room/list/RoomListSectionBuilderGroup.kt index bd43a83f2c..80c7b4e921 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/list/RoomListSectionBuilderGroup.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/list/RoomListSectionBuilderGroup.kt @@ -70,11 +70,11 @@ class RoomListSectionBuilderGroup( }, { qpm -> val name = stringProvider.getString(R.string.bottom_action_rooms) - val updatableFilterLivePageResult = session.getFilteredPagedRoomSummariesLive(qpm) + val updatableFilterLivePageResult = session.roomService().getFilteredPagedRoomSummariesLive(qpm) onUpdatable(updatableFilterLivePageResult) val itemCountFlow = updatableFilterLivePageResult.livePagedList.asFlow() - .flatMapLatest { session.getRoomCountLive(updatableFilterLivePageResult.queryParams).asFlow() } + .flatMapLatest { session.roomService().getRoomCountLive(updatableFilterLivePageResult.queryParams).asFlow() } .distinctUntilChanged() sections.add( @@ -197,7 +197,8 @@ class RoomListSectionBuilderGroup( actualGroupId: String? ) { if (autoAcceptInvites.showInvites()) { - addSection(sections, + addSection( + sections, activeSpaceAwareQueries, R.string.invitations_header, true @@ -252,7 +253,7 @@ class RoomListSectionBuilderGroup( query: (RoomSummaryQueryParams.Builder) -> Unit) { withQueryParams(query) { roomQueryParams -> val name = stringProvider.getString(nameRes) - session.getFilteredPagedRoomSummariesLive(roomQueryParams) + session.roomService().getFilteredPagedRoomSummariesLive(roomQueryParams) .also { activeSpaceUpdaters.add(it) }.livePagedList @@ -262,7 +263,7 @@ class RoomListSectionBuilderGroup( .onEach { sections.find { it.sectionName == name } ?.notificationCount - ?.postValue(session.getNotificationCountForRooms(roomQueryParams)) + ?.postValue(session.roomService().getNotificationCountForRooms(roomQueryParams)) } .flowOn(Dispatchers.Default) .launchIn(coroutineScope) @@ -272,7 +273,7 @@ class RoomListSectionBuilderGroup( sectionName = name, livePages = livePagedList, notifyOfLocalEcho = notifyOfLocalEcho, - itemCount = session.getRoomCountLive(roomQueryParams).asFlow() + itemCount = session.roomService().getRoomCountLive(roomQueryParams).asFlow() ) ) } diff --git a/vector/src/main/java/im/vector/app/features/home/room/list/RoomListSectionBuilderSpace.kt b/vector/src/main/java/im/vector/app/features/home/room/list/RoomListSectionBuilderSpace.kt index d405bc5b6f..59137ec490 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/list/RoomListSectionBuilderSpace.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/list/RoomListSectionBuilderSpace.kt @@ -47,6 +47,7 @@ import org.matrix.android.sdk.api.query.ActiveSpaceFilter import org.matrix.android.sdk.api.query.RoomCategoryFilter import org.matrix.android.sdk.api.query.RoomTagQueryFilter import org.matrix.android.sdk.api.session.Session +import org.matrix.android.sdk.api.session.getRoomSummary import org.matrix.android.sdk.api.session.room.RoomSummaryQueryParams import org.matrix.android.sdk.api.session.room.UpdatableLivePageResult import org.matrix.android.sdk.api.session.room.model.Membership @@ -324,11 +325,11 @@ class RoomListSectionBuilderSpace( }, { qpm -> val name = stringProvider.getString(R.string.bottom_action_rooms) - val updatableFilterLivePageResult = session.getFilteredPagedRoomSummariesLive(qpm) + val updatableFilterLivePageResult = session.roomService().getFilteredPagedRoomSummariesLive(qpm) onUpdatable(updatableFilterLivePageResult) val itemCountFlow = updatableFilterLivePageResult.livePagedList.asFlow() - .flatMapLatest { session.getRoomCountLive(updatableFilterLivePageResult.queryParams).asFlow() } + .flatMapLatest { session.roomService().getRoomCountLive(updatableFilterLivePageResult.queryParams).asFlow() } .distinctUntilChanged() sections.add( @@ -354,13 +355,13 @@ class RoomListSectionBuilderSpace( val liveQueryParams = MutableStateFlow(updatedQueryParams) val itemCountFlow = liveQueryParams .flatMapLatest { - session.getRoomCountLive(it).asFlow() + session.roomService().getRoomCountLive(it).asFlow() } .flowOn(Dispatchers.Main) .distinctUntilChanged() val name = stringProvider.getString(nameRes) - val filteredPagedRoomSummariesLive = session.getFilteredPagedRoomSummariesLive( + val filteredPagedRoomSummariesLive = session.roomService().getFilteredPagedRoomSummariesLive( roomQueryParams.process(spaceFilterStrategy, appStateHandler.safeActiveSpaceId()), pagedListConfig ) @@ -407,7 +408,7 @@ class RoomListSectionBuilderSpace( if (countRoomAsNotif) { RoomAggregateNotificationCount(it.size, it.size) } else { - session.getNotificationCountForRooms( + session.roomService().getNotificationCountForRooms( roomQueryParams.process(spaceFilterStrategy, appStateHandler.safeActiveSpaceId()) ) } diff --git a/vector/src/main/java/im/vector/app/features/home/room/list/RoomListViewEvents.kt b/vector/src/main/java/im/vector/app/features/home/room/list/RoomListViewEvents.kt index 15e16c464f..386352be9b 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/list/RoomListViewEvents.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/list/RoomListViewEvents.kt @@ -21,7 +21,7 @@ import im.vector.app.core.platform.VectorViewEvents import org.matrix.android.sdk.api.session.room.model.RoomSummary /** - * Transient events for RoomList + * Transient events for RoomList. */ sealed class RoomListViewEvents : VectorViewEvents { data class Loading(val message: CharSequence? = null) : RoomListViewEvents() diff --git a/vector/src/main/java/im/vector/app/features/home/room/list/RoomListViewModel.kt b/vector/src/main/java/im/vector/app/features/home/room/list/RoomListViewModel.kt index 70974bc1f6..73540ac76a 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/list/RoomListViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/list/RoomListViewModel.kt @@ -32,6 +32,8 @@ import im.vector.app.core.di.hiltMavericksViewModelFactory import im.vector.app.core.platform.VectorViewModel import im.vector.app.core.resources.StringProvider import im.vector.app.features.analytics.AnalyticsTracker +import im.vector.app.features.analytics.extensions.toAnalyticsJoinedRoom +import im.vector.app.features.analytics.plan.JoinedRoom import im.vector.app.features.displayname.getBestName import im.vector.app.features.invite.AutoAcceptInvites import im.vector.app.features.settings.VectorPreferences @@ -42,6 +44,8 @@ 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.session.Session +import org.matrix.android.sdk.api.session.getRoom +import org.matrix.android.sdk.api.session.getRoomSummary import org.matrix.android.sdk.api.session.room.UpdatableLivePageResult import org.matrix.android.sdk.api.session.room.members.ChangeMembershipState import org.matrix.android.sdk.api.session.room.model.tag.RoomTag @@ -86,7 +90,7 @@ class RoomListViewModel @AssistedInject constructor( */ ALL_IF_SPACE_NULL, - /** Do not filter based on space*/ + /** Do not filter based on space. */ NONE } @@ -166,7 +170,7 @@ class RoomListViewModel @AssistedInject constructor( } fun isPublicRoom(roomId: String): Boolean { - return session.getRoom(roomId)?.isPublic().orFalse() + return session.getRoom(roomId)?.stateService()?.isPublic().orFalse() } // PRIVATE METHODS ***************************************************************************** @@ -233,7 +237,7 @@ class RoomListViewModel @AssistedInject constructor( viewModelScope.launch { try { - session.leaveRoom(roomId) + session.roomService().leaveRoom(roomId) // We do not update the rejectingRoomsIds here, because, the room is not rejected yet regarding the sync data. // Instead, we wait for the room to be rejected // Known bug: if the user is invited again (after rejecting the first invitation), the loading will be displayed instead of the buttons. @@ -250,7 +254,7 @@ class RoomListViewModel @AssistedInject constructor( if (room != null) { viewModelScope.launch { try { - room.setRoomNotificationState(action.notificationState) + room.roomPushRuleService().setRoomNotificationState(action.notificationState) } catch (failure: Exception) { _viewEvents.post(RoomListViewEvents.Failure(failure)) } @@ -265,7 +269,7 @@ class RoomListViewModel @AssistedInject constructor( viewModelScope.launch { try { - session.joinRoom(action.roomId, null, action.viaServers ?: emptyList()) + session.roomService().joinRoom(action.roomId, null, action.viaServers ?: emptyList()) suggestedRoomJoiningState.postValue(suggestedRoomJoiningState.value.orEmpty().toMutableMap().apply { this[action.roomId] = Success(Unit) @@ -275,6 +279,8 @@ class RoomListViewModel @AssistedInject constructor( this[action.roomId] = Fail(failure) }.toMap()) } + session.getRoomSummary(action.roomId) + ?.let { analyticsTracker.capture(it.toAnalyticsJoinedRoom(JoinedRoom.Trigger.RoomDirectory)) } } } @@ -293,13 +299,13 @@ class RoomListViewModel @AssistedInject constructor( action.tag.otherTag() ?.takeIf { room.roomSummary()?.hasTag(it).orFalse() } ?.let { tagToRemove -> - room.deleteTag(tagToRemove) + room.tagsService().deleteTag(tagToRemove) } // Set the tag. We do not handle the order for the moment - room.addTag(action.tag, 0.5) + room.tagsService().addTag(action.tag, 0.5) } else { - room.deleteTag(action.tag) + room.tagsService().deleteTag(action.tag) } } catch (failure: Throwable) { _viewEvents.post(RoomListViewEvents.Failure(failure)) @@ -319,7 +325,7 @@ class RoomListViewModel @AssistedInject constructor( private fun handleLeaveRoom(action: RoomListAction.LeaveRoom) { _viewEvents.post(RoomListViewEvents.Loading(null)) viewModelScope.launch { - val value = runCatching { session.leaveRoom(action.roomId) } + val value = runCatching { session.roomService().leaveRoom(action.roomId) } .fold({ RoomListViewEvents.Done }, { RoomListViewEvents.Failure(it) }) _viewEvents.post(value) } diff --git a/vector/src/main/java/im/vector/app/features/home/room/list/SpaceChildInfoItem.kt b/vector/src/main/java/im/vector/app/features/home/room/list/SpaceChildInfoItem.kt index 99cbd45294..a2cb905f1b 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/list/SpaceChildInfoItem.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/list/SpaceChildInfoItem.kt @@ -40,7 +40,7 @@ import me.gujun.android.span.image import me.gujun.android.span.span import org.matrix.android.sdk.api.util.MatrixItem -@EpoxyModelClass(layout = R.layout.item_suggested_room) +@EpoxyModelClass(layout = R.layout.item_explore_space_child) abstract class SpaceChildInfoItem : VectorEpoxyModel() { @EpoxyAttribute lateinit var avatarRenderer: AvatarRenderer @@ -51,6 +51,7 @@ abstract class SpaceChildInfoItem : VectorEpoxyModel( @EpoxyAttribute var memberCount: Int = 0 @EpoxyAttribute var loading: Boolean = false + @EpoxyAttribute var suggested: Boolean = false @EpoxyAttribute var buttonLabel: String? = null @EpoxyAttribute var errorLabel: String? = null @@ -89,6 +90,7 @@ abstract class SpaceChildInfoItem : VectorEpoxyModel( } } + holder.suggestedTag.visibility = if (suggested) View.VISIBLE else View.GONE holder.joinButton.text = buttonLabel if (loading) { @@ -121,7 +123,8 @@ abstract class SpaceChildInfoItem : VectorEpoxyModel( val titleView by bind(R.id.roomNameView) val joinButton by bind
+ *
. * @param view * @param url * @return */ + @Deprecated("Deprecated in Java") override fun shouldOverrideUrlLoading(view: WebView, url: String?): Boolean { if (url == null) return super.shouldOverrideUrlLoading(view, url as String?) diff --git a/vector/src/main/java/im/vector/app/features/login/ReAuthHelper.kt b/vector/src/main/java/im/vector/app/features/login/ReAuthHelper.kt index f379048568..b29c930234 100644 --- a/vector/src/main/java/im/vector/app/features/login/ReAuthHelper.kt +++ b/vector/src/main/java/im/vector/app/features/login/ReAuthHelper.kt @@ -21,7 +21,7 @@ import javax.inject.Inject import javax.inject.Singleton /** - * Will store the account password for 3 minutes + * Will store the account password for 3 minutes. */ @Singleton class ReAuthHelper @Inject constructor() : TemporaryStore() diff --git a/vector/src/main/java/im/vector/app/features/login/SocialLoginButtonsView.kt b/vector/src/main/java/im/vector/app/features/login/SocialLoginButtonsView.kt index 515c8e9d39..68fc2d1c59 100644 --- a/vector/src/main/java/im/vector/app/features/login/SocialLoginButtonsView.kt +++ b/vector/src/main/java/im/vector/app/features/login/SocialLoginButtonsView.kt @@ -28,7 +28,7 @@ import im.vector.app.R import org.matrix.android.sdk.api.auth.data.SsoIdentityProvider class SocialLoginButtonsView @JvmOverloads constructor(context: Context, attrs: AttributeSet? = null, defStyle: Int = 0) : - LinearLayout(context, attrs, defStyle) { + LinearLayout(context, attrs, defStyle) { fun interface InteractionListener { fun onProviderSelected(id: String?) @@ -99,10 +99,10 @@ class SocialLoginButtonsView @JvmOverloads constructor(context: Context, attrs: SsoIdentityProvider.BRAND_TWITTER -> { MaterialButton(context, null, R.attr.vctr_social_login_button_twitter_style) } - SsoIdentityProvider.BRAND_GITLAB -> { + SsoIdentityProvider.BRAND_GITLAB -> { MaterialButton(context, null, R.attr.vctr_social_login_button_gitlab_style) } - else -> { + else -> { // TODO Use iconUrl MaterialButton(context, null, R.attr.materialButtonOutlinedStyle).apply { transformationMethod = null diff --git a/vector/src/main/java/im/vector/app/features/login/SupportedStage.kt b/vector/src/main/java/im/vector/app/features/login/SupportedStage.kt index 5ff00f7e85..f338f04401 100644 --- a/vector/src/main/java/im/vector/app/features/login/SupportedStage.kt +++ b/vector/src/main/java/im/vector/app/features/login/SupportedStage.kt @@ -19,7 +19,7 @@ package im.vector.app.features.login import org.matrix.android.sdk.api.auth.registration.Stage /** - * Stage.Other is not supported, as well as any other new stages added to the SDK before it is added to the list below + * Stage.Other is not supported, as well as any other new stages added to the SDK before it is added to the list below. */ fun Stage.isSupported(): Boolean { return this is Stage.ReCaptcha || diff --git a/vector/src/main/java/im/vector/app/features/login/terms/LoginTermsFragment.kt b/vector/src/main/java/im/vector/app/features/login/terms/LoginTermsFragment.kt index 262b79226e..ce499f290b 100755 --- a/vector/src/main/java/im/vector/app/features/login/terms/LoginTermsFragment.kt +++ b/vector/src/main/java/im/vector/app/features/login/terms/LoginTermsFragment.kt @@ -40,7 +40,7 @@ data class LoginTermsFragmentArgument( ) : Parcelable /** - * LoginTermsFragment displays the list of policies the user has to accept + * LoginTermsFragment displays the list of policies the user has to accept. */ class LoginTermsFragment @Inject constructor( private val policyController: PolicyController diff --git a/vector/src/main/java/im/vector/app/features/login2/AbstractLoginFragment2.kt b/vector/src/main/java/im/vector/app/features/login2/AbstractLoginFragment2.kt index 68568d1420..0b82ea445d 100644 --- a/vector/src/main/java/im/vector/app/features/login2/AbstractLoginFragment2.kt +++ b/vector/src/main/java/im/vector/app/features/login2/AbstractLoginFragment2.kt @@ -32,7 +32,7 @@ import kotlinx.coroutines.CancellationException import org.matrix.android.sdk.api.failure.Failure /** - * Parent Fragment for all the login/registration screens + * Parent Fragment for all the login/registration screens. */ abstract class AbstractLoginFragment2 : VectorBaseFragment(), OnBackPressed { diff --git a/vector/src/main/java/im/vector/app/features/login2/LoginCaptchaFragment2.kt b/vector/src/main/java/im/vector/app/features/login2/LoginCaptchaFragment2.kt index f729584f51..5fabe0ca32 100644 --- a/vector/src/main/java/im/vector/app/features/login2/LoginCaptchaFragment2.kt +++ b/vector/src/main/java/im/vector/app/features/login2/LoginCaptchaFragment2.kt @@ -44,7 +44,7 @@ import java.util.Formatter import javax.inject.Inject /** - * In this screen, the user is asked to confirm he is not a robot + * In this screen, the user is asked to confirm he is not a robot. */ class LoginCaptchaFragment2 @Inject constructor( private val assetReader: AssetReader @@ -152,12 +152,14 @@ class LoginCaptchaFragment2 @Inject constructor( } } + @Deprecated("Deprecated in Java") override fun onReceivedError(view: WebView, errorCode: Int, description: String, failingUrl: String) { @Suppress("DEPRECATION") super.onReceivedError(view, errorCode, description, failingUrl) onError(description) } + @Deprecated("Deprecated in Java") override fun shouldOverrideUrlLoading(view: WebView?, url: String?): Boolean { if (url?.startsWith("js:") == true) { var json = url.substring(3) diff --git a/vector/src/main/java/im/vector/app/features/login2/LoginFragmentSigninPassword2.kt b/vector/src/main/java/im/vector/app/features/login2/LoginFragmentSigninPassword2.kt index 5c9cefd2db..34bebd655a 100644 --- a/vector/src/main/java/im/vector/app/features/login2/LoginFragmentSigninPassword2.kt +++ b/vector/src/main/java/im/vector/app/features/login2/LoginFragmentSigninPassword2.kt @@ -157,7 +157,7 @@ class LoginFragmentSigninPassword2 @Inject constructor( } /** - * Detect if password ends or starts with spaces + * Detect if password ends or starts with spaces. */ private fun spaceInPassword() = views.passwordField.text.toString().let { it.trim() != it } } diff --git a/vector/src/main/java/im/vector/app/features/login2/LoginFragmentSigninUsername2.kt b/vector/src/main/java/im/vector/app/features/login2/LoginFragmentSigninUsername2.kt index b90887dba1..cb346451de 100644 --- a/vector/src/main/java/im/vector/app/features/login2/LoginFragmentSigninUsername2.kt +++ b/vector/src/main/java/im/vector/app/features/login2/LoginFragmentSigninUsername2.kt @@ -36,7 +36,7 @@ import javax.inject.Inject /** * In this screen: - * - the user is asked for its matrix ID, and have the possibility to open the screen to select a server + * - the user is asked for its matrix ID, and have the possibility to open the screen to select a server. */ class LoginFragmentSigninUsername2 @Inject constructor() : AbstractLoginFragment2() { diff --git a/vector/src/main/java/im/vector/app/features/login2/LoginFragmentToAny2.kt b/vector/src/main/java/im/vector/app/features/login2/LoginFragmentToAny2.kt index 3fa0e6c549..064889876b 100644 --- a/vector/src/main/java/im/vector/app/features/login2/LoginFragmentToAny2.kt +++ b/vector/src/main/java/im/vector/app/features/login2/LoginFragmentToAny2.kt @@ -195,7 +195,7 @@ class LoginFragmentToAny2 @Inject constructor() : AbstractSSOLoginFragment2() { @@ -75,8 +75,8 @@ class LoginGenericTextInputFormFragment2 @Inject constructor() : AbstractLoginFr if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { views.loginGenericTextInputFormTextInput.setAutofillHints( when (params.mode) { - TextInputFormFragmentMode.SetEmail -> HintConstants.AUTOFILL_HINT_EMAIL_ADDRESS - TextInputFormFragmentMode.SetMsisdn -> HintConstants.AUTOFILL_HINT_PHONE_NUMBER + TextInputFormFragmentMode.SetEmail -> HintConstants.AUTOFILL_HINT_EMAIL_ADDRESS + TextInputFormFragmentMode.SetMsisdn -> HintConstants.AUTOFILL_HINT_PHONE_NUMBER TextInputFormFragmentMode.ConfirmMsisdn -> HintConstants.AUTOFILL_HINT_SMS_OTP } ) diff --git a/vector/src/main/java/im/vector/app/features/login2/LoginResetPasswordFragment2.kt b/vector/src/main/java/im/vector/app/features/login2/LoginResetPasswordFragment2.kt index 038a12bea7..7916d9bbf2 100644 --- a/vector/src/main/java/im/vector/app/features/login2/LoginResetPasswordFragment2.kt +++ b/vector/src/main/java/im/vector/app/features/login2/LoginResetPasswordFragment2.kt @@ -40,7 +40,7 @@ import reactivecircus.flowbinding.android.widget.textChanges import javax.inject.Inject /** - * In this screen, the user is asked for email and new password to reset his password + * In this screen, the user is asked for email and new password to reset his password. */ class LoginResetPasswordFragment2 @Inject constructor() : AbstractLoginFragment2() { diff --git a/vector/src/main/java/im/vector/app/features/login2/LoginResetPasswordMailConfirmationFragment2.kt b/vector/src/main/java/im/vector/app/features/login2/LoginResetPasswordMailConfirmationFragment2.kt index b5b091f530..de1bcb8eea 100644 --- a/vector/src/main/java/im/vector/app/features/login2/LoginResetPasswordMailConfirmationFragment2.kt +++ b/vector/src/main/java/im/vector/app/features/login2/LoginResetPasswordMailConfirmationFragment2.kt @@ -27,7 +27,7 @@ import org.matrix.android.sdk.api.failure.is401 import javax.inject.Inject /** - * In this screen, the user is asked to check his email and to click on a button once it's done + * In this screen, the user is asked to check their email and to click on a button once it's done. */ class LoginResetPasswordMailConfirmationFragment2 @Inject constructor() : AbstractLoginFragment2() { diff --git a/vector/src/main/java/im/vector/app/features/login2/LoginResetPasswordSuccessFragment2.kt b/vector/src/main/java/im/vector/app/features/login2/LoginResetPasswordSuccessFragment2.kt index 25c27fa9ed..33ebd13f2a 100644 --- a/vector/src/main/java/im/vector/app/features/login2/LoginResetPasswordSuccessFragment2.kt +++ b/vector/src/main/java/im/vector/app/features/login2/LoginResetPasswordSuccessFragment2.kt @@ -24,7 +24,7 @@ import im.vector.app.databinding.FragmentLoginResetPasswordSuccess2Binding import javax.inject.Inject /** - * In this screen, we confirm to the user that his password has been reset + * In this screen, we confirm to the user that his password has been reset. */ class LoginResetPasswordSuccessFragment2 @Inject constructor() : AbstractLoginFragment2() { diff --git a/vector/src/main/java/im/vector/app/features/login2/LoginServerSelectionFragment2.kt b/vector/src/main/java/im/vector/app/features/login2/LoginServerSelectionFragment2.kt index 60e381b047..27fb74c3b0 100644 --- a/vector/src/main/java/im/vector/app/features/login2/LoginServerSelectionFragment2.kt +++ b/vector/src/main/java/im/vector/app/features/login2/LoginServerSelectionFragment2.kt @@ -28,7 +28,7 @@ import im.vector.app.features.login.EMS_LINK import javax.inject.Inject /** - * In this screen, the user will choose between matrix.org, or other type of homeserver + * In this screen, the user will choose between matrix.org, or other type of homeserver. */ class LoginServerSelectionFragment2 @Inject constructor() : AbstractLoginFragment2() { diff --git a/vector/src/main/java/im/vector/app/features/login2/LoginServerUrlFormFragment2.kt b/vector/src/main/java/im/vector/app/features/login2/LoginServerUrlFormFragment2.kt index 0732d176ac..0561594eeb 100644 --- a/vector/src/main/java/im/vector/app/features/login2/LoginServerUrlFormFragment2.kt +++ b/vector/src/main/java/im/vector/app/features/login2/LoginServerUrlFormFragment2.kt @@ -41,7 +41,7 @@ import javax.inject.Inject import javax.net.ssl.HttpsURLConnection /** - * In this screen, the user is prompted to enter a homeserver url + * In this screen, the user is prompted to enter a homeserver url. */ class LoginServerUrlFormFragment2 @Inject constructor() : AbstractLoginFragment2() { @@ -81,11 +81,13 @@ class LoginServerUrlFormFragment2 @Inject constructor() : AbstractLoginFragment2 private fun setupUi(state: LoginViewState2) { val completions = state.knownCustomHomeServersUrls + if (BuildConfig.DEBUG) listOf("http://10.0.2.2:8080") else emptyList() - views.loginServerUrlFormHomeServerUrl.setAdapter(ArrayAdapter( - requireContext(), - R.layout.item_completion_homeserver, - completions - )) + views.loginServerUrlFormHomeServerUrl.setAdapter( + ArrayAdapter( + requireContext(), + R.layout.item_completion_homeserver, + completions + ) + ) views.loginServerUrlFormHomeServerUrlTil.endIconMode = TextInputLayout.END_ICON_DROPDOWN_MENU .takeIf { completions.isNotEmpty() } ?: TextInputLayout.END_ICON_NONE diff --git a/vector/src/main/java/im/vector/app/features/login2/LoginSplashSignUpSignInSelectionFragment2.kt b/vector/src/main/java/im/vector/app/features/login2/LoginSplashSignUpSignInSelectionFragment2.kt index db6e86250b..611b5f4cd2 100644 --- a/vector/src/main/java/im/vector/app/features/login2/LoginSplashSignUpSignInSelectionFragment2.kt +++ b/vector/src/main/java/im/vector/app/features/login2/LoginSplashSignUpSignInSelectionFragment2.kt @@ -28,8 +28,8 @@ import im.vector.app.features.settings.VectorPreferences import javax.inject.Inject /** - * In this screen, the user is asked to sign up or to sign in to the homeserver - * This is the new splash screen + * In this screen, the user is asked to sign up or to sign in to the homeserver. + * This is the new splash screen. */ class LoginSplashSignUpSignInSelectionFragment2 @Inject constructor( private val vectorPreferences: VectorPreferences diff --git a/vector/src/main/java/im/vector/app/features/login2/LoginSsoOnlyFragment2.kt b/vector/src/main/java/im/vector/app/features/login2/LoginSsoOnlyFragment2.kt index 4e41ed20ac..7aa2150c98 100644 --- a/vector/src/main/java/im/vector/app/features/login2/LoginSsoOnlyFragment2.kt +++ b/vector/src/main/java/im/vector/app/features/login2/LoginSsoOnlyFragment2.kt @@ -28,7 +28,7 @@ import im.vector.app.features.login.SSORedirectRouterActivity import javax.inject.Inject /** - * In this screen, the user is asked to sign up or to sign in to the homeserver + * In this screen, the user is asked to sign up or to sign in to the homeserver. */ class LoginSsoOnlyFragment2 @Inject constructor() : AbstractSSOLoginFragment2() { diff --git a/vector/src/main/java/im/vector/app/features/login2/LoginViewEvents2.kt b/vector/src/main/java/im/vector/app/features/login2/LoginViewEvents2.kt index ee145d786f..11a441923e 100644 --- a/vector/src/main/java/im/vector/app/features/login2/LoginViewEvents2.kt +++ b/vector/src/main/java/im/vector/app/features/login2/LoginViewEvents2.kt @@ -21,7 +21,7 @@ import im.vector.app.core.platform.VectorViewEvents import org.matrix.android.sdk.api.auth.registration.FlowResult /** - * Transient events for Login + * Transient events for Login. */ sealed class LoginViewEvents2 : VectorViewEvents { data class Failure(val throwable: Throwable) : LoginViewEvents2() diff --git a/vector/src/main/java/im/vector/app/features/login2/LoginViewModel2.kt b/vector/src/main/java/im/vector/app/features/login2/LoginViewModel2.kt index 62f0007104..e72e3a1790 100644 --- a/vector/src/main/java/im/vector/app/features/login2/LoginViewModel2.kt +++ b/vector/src/main/java/im/vector/app/features/login2/LoginViewModel2.kt @@ -310,7 +310,7 @@ class LoginViewModel2 @AssistedInject constructor( } /** - * Check that the user name is available + * Check that the user name is available. */ private fun handleSetUserNameForSignUp(action: LoginAction2.SetUserName) { setState { copy(isLoading = true) } @@ -562,7 +562,7 @@ class LoginViewModel2 @AssistedInject constructor( } /** - * Perform wellknown request + * Perform wellknown request. */ private fun handleSetUserNameForSignIn(action: LoginAction2.SetUserName, homeServerConnectionConfig: HomeServerConnectionConfig?) { setState { copy(isLoading = true) } diff --git a/vector/src/main/java/im/vector/app/features/login2/LoginWaitForEmailFragment2.kt b/vector/src/main/java/im/vector/app/features/login2/LoginWaitForEmailFragment2.kt index 0cac52b306..772db7be5f 100644 --- a/vector/src/main/java/im/vector/app/features/login2/LoginWaitForEmailFragment2.kt +++ b/vector/src/main/java/im/vector/app/features/login2/LoginWaitForEmailFragment2.kt @@ -28,7 +28,7 @@ import org.matrix.android.sdk.api.failure.is401 import javax.inject.Inject /** - * In this screen, the user is asked to check his emails + * In this screen, the user is asked to check their emails. */ class LoginWaitForEmailFragment2 @Inject constructor() : AbstractLoginFragment2() { diff --git a/vector/src/main/java/im/vector/app/features/login2/LoginWebFragment2.kt b/vector/src/main/java/im/vector/app/features/login2/LoginWebFragment2.kt index 789cd459ac..cf8952b944 100644 --- a/vector/src/main/java/im/vector/app/features/login2/LoginWebFragment2.kt +++ b/vector/src/main/java/im/vector/app/features/login2/LoginWebFragment2.kt @@ -46,7 +46,7 @@ import javax.inject.Inject /** * This screen is displayed when the application does not support login flow or registration flow - * of the homeserver, as a fallback to login or to create an account + * of the homeserver, as a fallback to login or to create an account. */ class LoginWebFragment2 @Inject constructor( private val assetReader: AssetReader @@ -142,6 +142,7 @@ class LoginWebFragment2 @Inject constructor( .show() } + @Deprecated("Deprecated in Java") override fun onReceivedError(view: WebView, errorCode: Int, description: String, failingUrl: String) { super.onReceivedError(view, errorCode, description, failingUrl) @@ -189,11 +190,13 @@ class LoginWebFragment2 @Inject constructor( * } * } * } + * . *
* @param view * @param url * @return */ + @Deprecated("Deprecated in Java") override fun shouldOverrideUrlLoading(view: WebView, url: String?): Boolean { if (url == null) return super.shouldOverrideUrlLoading(view, url as String?) diff --git a/vector/src/main/java/im/vector/app/features/login2/created/AccountCreatedFragment.kt b/vector/src/main/java/im/vector/app/features/login2/created/AccountCreatedFragment.kt index 8223053ad8..d549c22028 100644 --- a/vector/src/main/java/im/vector/app/features/login2/created/AccountCreatedFragment.kt +++ b/vector/src/main/java/im/vector/app/features/login2/created/AccountCreatedFragment.kt @@ -31,6 +31,7 @@ import im.vector.app.core.date.VectorDateFormatter import im.vector.app.core.dialogs.GalleryOrCameraDialogHelper import im.vector.app.core.intent.getFilenameFromUri import im.vector.app.core.resources.ColorProvider +import im.vector.app.core.time.Clock import im.vector.app.databinding.DialogBaseEditTextBinding import im.vector.app.databinding.FragmentLoginAccountCreatedBinding import im.vector.app.features.displayname.getBestName @@ -46,19 +47,20 @@ import javax.inject.Inject /** * In this screen: - * - the account has been created and we propose the user to set an avatar and a display name + * - the account has been created and we propose the user to set an avatar and a display name. */ class AccountCreatedFragment @Inject constructor( private val avatarRenderer: AvatarRenderer, private val dateFormatter: VectorDateFormatter, private val matrixItemColorProvider: MatrixItemColorProvider, + private val clock: Clock, colorProvider: ColorProvider ) : AbstractLoginFragment2(), GalleryOrCameraDialogHelper.Listener { private val viewModel: AccountCreatedViewModel by fragmentViewModel() - private val galleryOrCameraDialogHelper = GalleryOrCameraDialogHelper(this, colorProvider) + private val galleryOrCameraDialogHelper = GalleryOrCameraDialogHelper(this, colorProvider, clock) override fun getBinding(inflater: LayoutInflater, container: ViewGroup?): FragmentLoginAccountCreatedBinding { return FragmentLoginAccountCreatedBinding.inflate(inflater, container, false) @@ -73,7 +75,7 @@ class AccountCreatedFragment @Inject constructor( viewModel.onEach { invalidateState(it) } - views.loginAccountCreatedTime.text = dateFormatter.format(System.currentTimeMillis(), DateFormatKind.MESSAGE_SIMPLE) + views.loginAccountCreatedTime.text = dateFormatter.format(clock.epochMillis(), DateFormatKind.MESSAGE_SIMPLE) } private fun observeViewEvents() { @@ -113,9 +115,11 @@ class AccountCreatedFragment @Inject constructor( override fun onImageReady(uri: Uri?) { uri ?: return - viewModel.handle(AccountCreatedAction.SetAvatar( - avatarUri = uri, - filename = getFilenameFromUri(requireContext(), uri) ?: UUID.randomUUID().toString()) + viewModel.handle( + AccountCreatedAction.SetAvatar( + avatarUri = uri, + filename = getFilenameFromUri(requireContext(), uri) ?: UUID.randomUUID().toString() + ) ) } diff --git a/vector/src/main/java/im/vector/app/features/login2/created/AccountCreatedViewEvents.kt b/vector/src/main/java/im/vector/app/features/login2/created/AccountCreatedViewEvents.kt index 4677e1abd5..6a23409f5c 100644 --- a/vector/src/main/java/im/vector/app/features/login2/created/AccountCreatedViewEvents.kt +++ b/vector/src/main/java/im/vector/app/features/login2/created/AccountCreatedViewEvents.kt @@ -20,7 +20,7 @@ package im.vector.app.features.login2.created import im.vector.app.core.platform.VectorViewEvents /** - * Transient events for Account Created + * Transient events for Account Created. */ sealed class AccountCreatedViewEvents : VectorViewEvents { data class Failure(val throwable: Throwable) : AccountCreatedViewEvents() diff --git a/vector/src/main/java/im/vector/app/features/login2/created/AccountCreatedViewModel.kt b/vector/src/main/java/im/vector/app/features/login2/created/AccountCreatedViewModel.kt index 568cdab119..ac4edba7fb 100644 --- a/vector/src/main/java/im/vector/app/features/login2/created/AccountCreatedViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/login2/created/AccountCreatedViewModel.kt @@ -81,7 +81,7 @@ class AccountCreatedViewModel @AssistedInject constructor( private fun handleSetAvatar(action: AccountCreatedAction.SetAvatar) { setState { copy(isLoading = true) } viewModelScope.launch { - val result = runCatching { session.updateAvatar(session.myUserId, action.avatarUri, action.filename) } + val result = runCatching { session.profileService().updateAvatar(session.myUserId, action.avatarUri, action.filename) } .onFailure { _viewEvents.post(AccountCreatedViewEvents.Failure(it)) } setState { copy( @@ -95,7 +95,7 @@ class AccountCreatedViewModel @AssistedInject constructor( private fun handleSetDisplayName(action: AccountCreatedAction.SetDisplayName) { setState { copy(isLoading = true) } viewModelScope.launch { - val result = runCatching { session.setDisplayName(session.myUserId, action.displayName) } + val result = runCatching { session.profileService().setDisplayName(session.myUserId, action.displayName) } .onFailure { _viewEvents.post(AccountCreatedViewEvents.Failure(it)) } setState { copy( diff --git a/vector/src/main/java/im/vector/app/features/login2/terms/LoginTermsFragment2.kt b/vector/src/main/java/im/vector/app/features/login2/terms/LoginTermsFragment2.kt index 303fc5ef17..a48996a16f 100755 --- a/vector/src/main/java/im/vector/app/features/login2/terms/LoginTermsFragment2.kt +++ b/vector/src/main/java/im/vector/app/features/login2/terms/LoginTermsFragment2.kt @@ -37,7 +37,7 @@ import org.matrix.android.sdk.api.auth.data.LocalizedFlowDataLoginTerms import javax.inject.Inject /** - * LoginTermsFragment displays the list of policies the user has to accept + * LoginTermsFragment displays the list of policies the user has to accept. */ class LoginTermsFragment2 @Inject constructor( private val policyController: PolicyController diff --git a/vector/src/main/java/im/vector/app/features/matrixto/MatrixToAction.kt b/vector/src/main/java/im/vector/app/features/matrixto/MatrixToAction.kt index f38049640d..25db811600 100644 --- a/vector/src/main/java/im/vector/app/features/matrixto/MatrixToAction.kt +++ b/vector/src/main/java/im/vector/app/features/matrixto/MatrixToAction.kt @@ -24,7 +24,7 @@ sealed class MatrixToAction : VectorViewModelAction { object FailedToResolveUser : MatrixToAction() object FailedToStartChatting : MatrixToAction() data class JoinSpace(val spaceID: String, val viaServers: List?) : MatrixToAction() - data class JoinRoom(val roomId: String, val viaServers: List?) : MatrixToAction() + data class JoinRoom(val roomIdOrAlias: String, val viaServers: List?) : MatrixToAction() data class OpenSpace(val spaceID: String) : MatrixToAction() data class OpenRoom(val roomId: String) : MatrixToAction() } diff --git a/vector/src/main/java/im/vector/app/features/matrixto/MatrixToBottomSheet.kt b/vector/src/main/java/im/vector/app/features/matrixto/MatrixToBottomSheet.kt index 61dcd48779..8c685b8178 100644 --- a/vector/src/main/java/im/vector/app/features/matrixto/MatrixToBottomSheet.kt +++ b/vector/src/main/java/im/vector/app/features/matrixto/MatrixToBottomSheet.kt @@ -32,6 +32,7 @@ import im.vector.app.R import im.vector.app.core.extensions.commitTransaction import im.vector.app.core.platform.VectorBaseBottomSheetDialogFragment import im.vector.app.databinding.BottomSheetMatrixToCardBinding +import im.vector.app.features.analytics.plan.ViewRoom import im.vector.app.features.home.AvatarRenderer import kotlinx.parcelize.Parcelize import org.matrix.android.sdk.api.session.permalinks.PermalinkData @@ -44,7 +45,8 @@ class MatrixToBottomSheet : @Parcelize data class MatrixToArgs( - val matrixToLink: String + val matrixToLink: String, + val origin: OriginOfMatrixTo ) : Parcelable @Inject lateinit var avatarRenderer: AvatarRenderer @@ -58,7 +60,7 @@ class MatrixToBottomSheet : private val viewModel by fragmentViewModel(MatrixToBottomSheetViewModel::class) interface InteractionListener { - fun mxToBottomSheetNavigateToRoom(roomId: String) + fun mxToBottomSheetNavigateToRoom(roomId: String, trigger: ViewRoom.Trigger?) fun mxToBottomSheetSwitchToSpace(spaceId: String) } @@ -82,7 +84,8 @@ class MatrixToBottomSheet : private fun showFragment(fragmentClass: KClass, bundle: Bundle) { if (childFragmentManager.findFragmentByTag(fragmentClass.simpleName) == null) { childFragmentManager.commitTransaction { - replace(views.matrixToCardFragmentContainer.id, + replace( + views.matrixToCardFragmentContainer.id, fragmentClass.java, bundle, fragmentClass.simpleName @@ -97,7 +100,9 @@ class MatrixToBottomSheet : viewModel.observeViewEvents { when (it) { is MatrixToViewEvents.NavigateToRoom -> { - interactionListener?.mxToBottomSheetNavigateToRoom(it.roomId) + withState(viewModel) { state -> + interactionListener?.mxToBottomSheetNavigateToRoom(it.roomId, state.origin.toViewRoomTrigger()) + } dismiss() } MatrixToViewEvents.Dismiss -> dismiss() @@ -116,9 +121,9 @@ class MatrixToBottomSheet : } companion object { - fun withLink(matrixToLink: String): MatrixToBottomSheet { + fun withLink(matrixToLink: String, origin: OriginOfMatrixTo): MatrixToBottomSheet { return MatrixToBottomSheet().apply { - setArguments(MatrixToArgs(matrixToLink = matrixToLink)) + setArguments(MatrixToArgs(matrixToLink = matrixToLink, origin = origin)) } } } diff --git a/vector/src/main/java/im/vector/app/features/matrixto/MatrixToBottomSheetState.kt b/vector/src/main/java/im/vector/app/features/matrixto/MatrixToBottomSheetState.kt index 0fff2495f9..a6eb2a9afc 100644 --- a/vector/src/main/java/im/vector/app/features/matrixto/MatrixToBottomSheetState.kt +++ b/vector/src/main/java/im/vector/app/features/matrixto/MatrixToBottomSheetState.kt @@ -30,12 +30,14 @@ data class MatrixToBottomSheetState( val matrixItem: Async = Uninitialized, val startChattingState: Async = Uninitialized, val roomPeekResult: Async = Uninitialized, - val peopleYouKnow: Async> = Uninitialized + val peopleYouKnow: Async> = Uninitialized, + val origin: OriginOfMatrixTo ) : MavericksState { constructor(args: MatrixToBottomSheet.MatrixToArgs) : this( deepLink = args.matrixToLink, - linkType = PermalinkParser.parse(args.matrixToLink) + linkType = PermalinkParser.parse(args.matrixToLink), + origin = args.origin ) } diff --git a/vector/src/main/java/im/vector/app/features/matrixto/MatrixToBottomSheetViewModel.kt b/vector/src/main/java/im/vector/app/features/matrixto/MatrixToBottomSheetViewModel.kt index 04c2c8dd44..22082a36e7 100644 --- a/vector/src/main/java/im/vector/app/features/matrixto/MatrixToBottomSheetViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/matrixto/MatrixToBottomSheetViewModel.kt @@ -30,11 +30,16 @@ import im.vector.app.core.di.hiltMavericksViewModelFactory import im.vector.app.core.error.ErrorFormatter import im.vector.app.core.platform.VectorViewModel import im.vector.app.core.resources.StringProvider +import im.vector.app.features.analytics.AnalyticsTracker +import im.vector.app.features.analytics.extensions.toAnalyticsJoinedRoom import im.vector.app.features.createdirect.DirectRoomHelper import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch +import org.matrix.android.sdk.api.MatrixPatterns import org.matrix.android.sdk.api.extensions.tryOrNull import org.matrix.android.sdk.api.session.Session +import org.matrix.android.sdk.api.session.getRoom +import org.matrix.android.sdk.api.session.getRoomSummary import org.matrix.android.sdk.api.session.permalinks.PermalinkData import org.matrix.android.sdk.api.session.room.model.Membership import org.matrix.android.sdk.api.session.room.peeking.PeekResult @@ -48,7 +53,8 @@ class MatrixToBottomSheetViewModel @AssistedInject constructor( private val session: Session, private val stringProvider: StringProvider, private val directRoomHelper: DirectRoomHelper, - private val errorFormatter: ErrorFormatter + private val errorFormatter: ErrorFormatter, + private val analyticsTracker: AnalyticsTracker ) : VectorViewModel(initialState) { @AssistedFactory @@ -109,7 +115,7 @@ class MatrixToBottomSheetViewModel @AssistedInject constructor( // could this room be already known val knownRoom = if (permalinkData.isRoomAlias) { tryOrNull { - session.getRoomIdByAlias(permalinkData.roomIdOrAlias, false) + session.roomService().getRoomIdByAlias(permalinkData.roomIdOrAlias, false) } ?.getOrNull() ?.roomId?.let { @@ -194,7 +200,7 @@ class MatrixToBottomSheetViewModel @AssistedInject constructor( private fun checkForKnownMembers(someMembers: List) { viewModelScope.launch(Dispatchers.Default) { val knownMembers = someMembers.filter { - session.getExistingDirectRoomWithUser(it.id) != null + session.roomService().getExistingDirectRoomWithUser(it.id) != null } // put one with avatar first, and take 5 val finalRes = (knownMembers.filter { it.avatarUrl != null } + knownMembers.filter { it.avatarUrl == null }) @@ -233,17 +239,17 @@ class MatrixToBottomSheetViewModel @AssistedInject constructor( } private suspend fun resolveUser(userId: String): User { - return tryOrNull { session.resolveUser(userId) } + return tryOrNull { session.userService().resolveUser(userId) } // Create raw user in case the user is not searchable ?: User(userId, null, null) } /** * Let's try to get some information about that room, - * main thing is trying to see if it's a space or a room + * main thing is trying to see if it's a space or a room. */ private suspend fun resolveRoom(roomIdOrAlias: String): PeekResult { - return session.peekRoom(roomIdOrAlias) + return session.roomService().peekRoom(roomIdOrAlias) } override fun handle(action: MatrixToAction) { @@ -273,6 +279,11 @@ class MatrixToBottomSheetViewModel @AssistedInject constructor( viewModelScope.launch { try { val joinResult = session.spaceService().joinSpace(joinSpace.spaceID, null, joinSpace.viaServers?.take(3) ?: emptyList()) + withState { state -> + session.getRoomSummary(joinSpace.spaceID)?.let { summary -> + analyticsTracker.capture(summary.toAnalyticsJoinedRoom(state.origin.toJoinedRoomTrigger())) + } + } if (joinResult.isSuccess()) { _viewEvents.post(MatrixToViewEvents.NavigateToSpace(joinSpace.spaceID)) } else { @@ -296,8 +307,19 @@ class MatrixToBottomSheetViewModel @AssistedInject constructor( } viewModelScope.launch { try { - session.joinRoom(action.roomId, null, action.viaServers?.take(3) ?: emptyList()) - _viewEvents.post(MatrixToViewEvents.NavigateToRoom(action.roomId)) + session.roomService().joinRoom( + roomIdOrAlias = action.roomIdOrAlias, + reason = null, + viaServers = action.viaServers?.take(3) ?: emptyList() + ) + + val roomId = getRoomIdFromRoomIdOrAlias(action.roomIdOrAlias) + withState { state -> + session.getRoomSummary(roomId)?.let { summary -> + analyticsTracker.capture(summary.toAnalyticsJoinedRoom(state.origin.toJoinedRoomTrigger())) + } + } + _viewEvents.post(MatrixToViewEvents.NavigateToRoom(roomId)) } catch (failure: Throwable) { _viewEvents.post(MatrixToViewEvents.ShowModalError(errorFormatter.toHumanReadable(failure))) } finally { @@ -309,6 +331,12 @@ class MatrixToBottomSheetViewModel @AssistedInject constructor( } } + private suspend fun getRoomIdFromRoomIdOrAlias(roomIdOrAlias: String): String { + return if (MatrixPatterns.isRoomAlias(roomIdOrAlias)) { + session.roomService().getRoomIdByAlias(roomIdOrAlias, true).get().roomId + } else roomIdOrAlias + } + private fun handleStartChatting(action: MatrixToAction.StartChattingWithUser) { setState { copy(startChattingState = Loading()) diff --git a/vector/src/main/java/im/vector/app/features/matrixto/MatrixToRoomSpaceFragment.kt b/vector/src/main/java/im/vector/app/features/matrixto/MatrixToRoomSpaceFragment.kt index 3b0fc175b0..4e3159b5b3 100644 --- a/vector/src/main/java/im/vector/app/features/matrixto/MatrixToRoomSpaceFragment.kt +++ b/vector/src/main/java/im/vector/app/features/matrixto/MatrixToRoomSpaceFragment.kt @@ -60,10 +60,10 @@ class MatrixToRoomSpaceFragment @Inject constructor( Uninitialized -> { views.matrixToCardContentVisibility.isVisible = false } - is Loading -> { + is Loading -> { views.matrixToCardContentVisibility.isVisible = false } - is Success -> { + is Success -> { views.matrixToCardContentVisibility.isVisible = true when (val peek = item.invoke()) { is RoomInfoResult.FullInfo -> { @@ -154,7 +154,7 @@ class MatrixToRoomSpaceFragment @Inject constructor( } } } - is Fail -> { + is Fail -> { // TODO display some error copy? sharedViewModel.handle(MatrixToAction.FailedToResolveUser) } @@ -176,14 +176,14 @@ class MatrixToRoomSpaceFragment @Inject constructor( Uninitialized -> { views.matrixToCardMainButton.render(ButtonStateView.State.Button) } - is Success -> { + is Success -> { views.matrixToCardMainButton.render(ButtonStateView.State.Button) } - is Fail -> { + is Fail -> { views.matrixToCardMainButton.render(ButtonStateView.State.Error) // TODO display some error copy? } - is Loading -> { + is Loading -> { views.matrixToCardMainButton.render(ButtonStateView.State.Loading) } } @@ -191,7 +191,7 @@ class MatrixToRoomSpaceFragment @Inject constructor( private fun mainButtonClicked() = withState(sharedViewModel) { state -> when (val info = state.roomPeekResult.invoke()) { - is RoomInfoResult.FullInfo -> { + is RoomInfoResult.FullInfo -> { when (info.membership) { Membership.NONE, Membership.INVITE, diff --git a/vector/src/main/java/im/vector/app/features/matrixto/MatrixToUserFragment.kt b/vector/src/main/java/im/vector/app/features/matrixto/MatrixToUserFragment.kt index 3792183bca..b0d68c57e8 100644 --- a/vector/src/main/java/im/vector/app/features/matrixto/MatrixToUserFragment.kt +++ b/vector/src/main/java/im/vector/app/features/matrixto/MatrixToUserFragment.kt @@ -60,16 +60,16 @@ class MatrixToUserFragment @Inject constructor( Uninitialized -> { views.matrixToCardUserContentVisibility.isVisible = false } - is Loading -> { + is Loading -> { views.matrixToCardUserContentVisibility.isVisible = false } - is Success -> { + is Success -> { views.matrixToCardUserContentVisibility.isVisible = true views.matrixToCardNameText.setTextOrHide(item.invoke().displayName) views.matrixToCardUserIdText.setTextOrHide(item.invoke().id) avatarRenderer.render(item.invoke(), views.matrixToCardAvatar) } - is Fail -> { + is Fail -> { // TODO display some error copy? sharedViewModel.handle(MatrixToAction.FailedToResolveUser) } @@ -80,17 +80,17 @@ class MatrixToUserFragment @Inject constructor( views.matrixToCardButtonLoading.isVisible = false views.matrixToCardSendMessageButton.isVisible = false } - is Success -> { + is Success -> { views.matrixToCardButtonLoading.isVisible = false views.matrixToCardSendMessageButton.isVisible = true } - is Fail -> { + is Fail -> { views.matrixToCardButtonLoading.isVisible = false views.matrixToCardSendMessageButton.isVisible = true // TODO display some error copy? sharedViewModel.handle(MatrixToAction.FailedToStartChatting) } - is Loading -> { + is Loading -> { views.matrixToCardButtonLoading.isVisible = true views.matrixToCardSendMessageButton.isInvisible = true } diff --git a/vector/src/main/java/im/vector/app/features/matrixto/OriginOfMatrixTo.kt b/vector/src/main/java/im/vector/app/features/matrixto/OriginOfMatrixTo.kt new file mode 100644 index 0000000000..297c8e7723 --- /dev/null +++ b/vector/src/main/java/im/vector/app/features/matrixto/OriginOfMatrixTo.kt @@ -0,0 +1,51 @@ +/* + * 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.matrixto + +import im.vector.app.features.analytics.plan.JoinedRoom +import im.vector.app.features.analytics.plan.ViewRoom + +enum class OriginOfMatrixTo { + LINK, + NOTIFICATION, + TIMELINE, + SPACE_EXPLORE, + ROOM_LIST, + USER_CODE +} + +fun OriginOfMatrixTo.toJoinedRoomTrigger(): JoinedRoom.Trigger? { + return when (this) { + OriginOfMatrixTo.LINK -> JoinedRoom.Trigger.MobilePermalink + OriginOfMatrixTo.NOTIFICATION -> JoinedRoom.Trigger.Notification + OriginOfMatrixTo.TIMELINE -> JoinedRoom.Trigger.Timeline + OriginOfMatrixTo.SPACE_EXPLORE -> JoinedRoom.Trigger.SpaceHierarchy + OriginOfMatrixTo.ROOM_LIST -> JoinedRoom.Trigger.RoomDirectory + OriginOfMatrixTo.USER_CODE -> null + } +} + +fun OriginOfMatrixTo.toViewRoomTrigger(): ViewRoom.Trigger? { + return when (this) { + OriginOfMatrixTo.LINK -> ViewRoom.Trigger.MobilePermalink + OriginOfMatrixTo.NOTIFICATION -> ViewRoom.Trigger.Notification + OriginOfMatrixTo.TIMELINE -> ViewRoom.Trigger.Timeline + OriginOfMatrixTo.SPACE_EXPLORE -> ViewRoom.Trigger.SpaceHierarchy + OriginOfMatrixTo.ROOM_LIST -> ViewRoom.Trigger.RoomDirectory + OriginOfMatrixTo.USER_CODE -> null + } +} diff --git a/vector/src/main/java/im/vector/app/features/matrixto/SpaceCardRenderer.kt b/vector/src/main/java/im/vector/app/features/matrixto/SpaceCardRenderer.kt index 2f71089a39..b72b36a564 100644 --- a/vector/src/main/java/im/vector/app/features/matrixto/SpaceCardRenderer.kt +++ b/vector/src/main/java/im/vector/app/features/matrixto/SpaceCardRenderer.kt @@ -140,7 +140,8 @@ class SpaceCardRenderer @Inject constructor( avatarRenderer.render(item, images[index]) } inCard.peopleYouMayKnowText.setTextOrHide( - stringProvider.getQuantityString(R.plurals.space_people_you_know, + stringProvider.getQuantityString( + R.plurals.space_people_you_know, peopleYouKnow.count(), peopleYouKnow.count() ) diff --git a/vector/src/main/java/im/vector/app/features/media/BigImageViewerActivity.kt b/vector/src/main/java/im/vector/app/features/media/BigImageViewerActivity.kt index a6b166815c..4a9304846e 100644 --- a/vector/src/main/java/im/vector/app/features/media/BigImageViewerActivity.kt +++ b/vector/src/main/java/im/vector/app/features/media/BigImageViewerActivity.kt @@ -27,7 +27,7 @@ import im.vector.app.databinding.ActivityBigImageViewerBinding import javax.inject.Inject /** - * Simple Activity to display an avatar in fullscreen + * Simple Activity to display an avatar in fullscreen. */ @AndroidEntryPoint class BigImageViewerActivity : VectorBaseActivity() { diff --git a/vector/src/main/java/im/vector/app/features/media/DataAttachmentRoomProvider.kt b/vector/src/main/java/im/vector/app/features/media/DataAttachmentRoomProvider.kt index b9d98429a7..29ec4f0629 100644 --- a/vector/src/main/java/im/vector/app/features/media/DataAttachmentRoomProvider.kt +++ b/vector/src/main/java/im/vector/app/features/media/DataAttachmentRoomProvider.kt @@ -23,6 +23,7 @@ import kotlinx.coroutines.CoroutineScope import org.matrix.android.sdk.api.extensions.tryOrNull import org.matrix.android.sdk.api.session.file.FileService import org.matrix.android.sdk.api.session.room.Room +import org.matrix.android.sdk.api.session.room.getTimelineEvent import org.matrix.android.sdk.api.session.room.timeline.TimelineEvent import org.matrix.android.sdk.api.util.MimeTypes import java.io.File diff --git a/vector/src/main/java/im/vector/app/features/media/ImageContentRenderer.kt b/vector/src/main/java/im/vector/app/features/media/ImageContentRenderer.kt index 50325327db..49bced7e81 100644 --- a/vector/src/main/java/im/vector/app/features/media/ImageContentRenderer.kt +++ b/vector/src/main/java/im/vector/app/features/media/ImageContentRenderer.kt @@ -91,7 +91,7 @@ class ImageContentRenderer @Inject constructor(private val localFilesHelper: Loc } /** - * For url preview + * For url preview. */ fun render(previewUrlData: PreviewUrlData, imageView: ImageView): Boolean { val contentUrlResolver = activeSessionHolder.getActiveSession().contentUrlResolver() @@ -112,7 +112,7 @@ class ImageContentRenderer @Inject constructor(private val localFilesHelper: Loc } /** - * For gallery + * For gallery. */ fun render(data: Data, imageView: ImageView, size: Int) { // a11y @@ -149,7 +149,7 @@ class ImageContentRenderer @Inject constructor(private val localFilesHelper: Loc } /** - * Used by Attachment Viewer + * Used by Attachment Viewer. */ fun render(data: Data, contextView: View, target: CustomViewTarget<*, Drawable>) { val req = if (data.elementToDecrypt != null) { diff --git a/vector/src/main/java/im/vector/app/features/media/RoomEventsAttachmentProvider.kt b/vector/src/main/java/im/vector/app/features/media/RoomEventsAttachmentProvider.kt index cd868c9f2f..e18a13a3e6 100644 --- a/vector/src/main/java/im/vector/app/features/media/RoomEventsAttachmentProvider.kt +++ b/vector/src/main/java/im/vector/app/features/media/RoomEventsAttachmentProvider.kt @@ -169,7 +169,8 @@ class RoomEventsAttachmentProvider( fileName = messageContent.body, mimeType = messageContent.mimeType, url = messageContent.getFileUrl(), - elementToDecrypt = messageContent.encryptedFileInfo?.toElementToDecrypt()) + elementToDecrypt = messageContent.encryptedFileInfo?.toElementToDecrypt() + ) } } } diff --git a/vector/src/main/java/im/vector/app/features/media/VectorAttachmentViewerActivity.kt b/vector/src/main/java/im/vector/app/features/media/VectorAttachmentViewerActivity.kt index d8c2b83f9b..2259eb513f 100644 --- a/vector/src/main/java/im/vector/app/features/media/VectorAttachmentViewerActivity.kt +++ b/vector/src/main/java/im/vector/app/features/media/VectorAttachmentViewerActivity.kt @@ -53,6 +53,7 @@ import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.launch import kotlinx.coroutines.withContext import kotlinx.parcelize.Parcelize +import org.matrix.android.sdk.api.session.getRoom import timber.log.Timber import javax.inject.Inject @@ -134,7 +135,7 @@ class VectorAttachmentViewerActivity : AttachmentViewerActivity(), AttachmentInt initialIndex = inMemoryData.indexOfFirst { it.eventId == args.eventId }.coerceAtLeast(0) dataSourceFactory.createProvider(inMemoryData, room, lifecycleScope) } else { - val events = room?.getAttachmentMessages().orEmpty() + val events = room?.timelineService()?.getAttachmentMessages().orEmpty() initialIndex = events.indexOfFirst { it.eventId == args.eventId }.coerceAtLeast(0) dataSourceFactory.createProvider(events, lifecycleScope) } diff --git a/vector/src/main/java/im/vector/app/features/media/VideoContentRenderer.kt b/vector/src/main/java/im/vector/app/features/media/VideoContentRenderer.kt index 4e6d19686b..c7bb54fcf4 100644 --- a/vector/src/main/java/im/vector/app/features/media/VideoContentRenderer.kt +++ b/vector/src/main/java/im/vector/app/features/media/VideoContentRenderer.kt @@ -87,7 +87,8 @@ class VideoContentRenderer @Inject constructor(private val localFilesHelper: Loc fileName = data.filename, mimeType = data.mimeType, url = data.url, - elementToDecrypt = data.elementToDecrypt) + elementToDecrypt = data.elementToDecrypt + ) } withContext(Dispatchers.Main) { result.fold( @@ -130,7 +131,8 @@ class VideoContentRenderer @Inject constructor(private val localFilesHelper: Loc fileName = data.filename, mimeType = data.mimeType, url = data.url, - elementToDecrypt = null) + elementToDecrypt = null + ) } withContext(Dispatchers.Main) { result.fold( diff --git a/vector/src/main/java/im/vector/app/features/media/domain/usecase/DownloadMediaUseCase.kt b/vector/src/main/java/im/vector/app/features/media/domain/usecase/DownloadMediaUseCase.kt index b0401ccd30..6a24ac4fd7 100644 --- a/vector/src/main/java/im/vector/app/features/media/domain/usecase/DownloadMediaUseCase.kt +++ b/vector/src/main/java/im/vector/app/features/media/domain/usecase/DownloadMediaUseCase.kt @@ -20,6 +20,7 @@ import android.content.Context import androidx.core.net.toUri import dagger.hilt.android.qualifiers.ApplicationContext import im.vector.app.core.intent.getMimeTypeFromUri +import im.vector.app.core.time.Clock import im.vector.app.core.utils.saveMedia import im.vector.app.features.notifications.NotificationUtils import kotlinx.coroutines.withContext @@ -30,7 +31,8 @@ import javax.inject.Inject class DownloadMediaUseCase @Inject constructor( @ApplicationContext private val appContext: Context, private val session: Session, - private val notificationUtils: NotificationUtils + private val notificationUtils: NotificationUtils, + private val clock: Clock, ) { suspend fun execute(input: File): Result = withContext(session.coroutineDispatchers.io) { @@ -40,7 +42,8 @@ class DownloadMediaUseCase @Inject constructor( file = input, title = input.name, mediaMimeType = getMimeTypeFromUri(appContext, input.toUri()), - notificationUtils = notificationUtils + notificationUtils = notificationUtils, + currentTimeMillis = clock.epochMillis() ) } } 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 6122cf1eee..7cc42ec57f 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 @@ -39,6 +39,9 @@ import im.vector.app.core.platform.VectorBaseActivity import im.vector.app.core.utils.toast import im.vector.app.features.VectorFeatures import im.vector.app.features.VectorFeatures.OnboardingVariant +import im.vector.app.features.analytics.AnalyticsTracker +import im.vector.app.features.analytics.extensions.toAnalyticsViewRoom +import im.vector.app.features.analytics.plan.ViewRoom import im.vector.app.features.analytics.ui.consent.AnalyticsOptInActivity import im.vector.app.features.call.conference.JitsiCallViewModel import im.vector.app.features.call.conference.VectorJitsiActivity @@ -68,6 +71,7 @@ import im.vector.app.features.location.LocationSharingMode import im.vector.app.features.login.LoginActivity import im.vector.app.features.login.LoginConfig import im.vector.app.features.matrixto.MatrixToBottomSheet +import im.vector.app.features.matrixto.OriginOfMatrixTo import im.vector.app.features.media.AttachmentData import im.vector.app.features.media.BigImageViewerActivity import im.vector.app.features.media.VectorAttachmentViewerActivity @@ -101,6 +105,8 @@ import im.vector.app.features.widgets.WidgetActivity import im.vector.app.features.widgets.WidgetArgsBuilder import im.vector.app.space import org.matrix.android.sdk.api.session.crypto.verification.IncomingSasVerificationTransaction +import org.matrix.android.sdk.api.session.getRoom +import org.matrix.android.sdk.api.session.getRoomSummary import org.matrix.android.sdk.api.session.permalinks.PermalinkData import org.matrix.android.sdk.api.session.room.model.roomdirectory.PublicRoom import org.matrix.android.sdk.api.session.terms.TermsService @@ -116,7 +122,8 @@ class DefaultNavigator @Inject constructor( private val widgetArgsBuilder: WidgetArgsBuilder, private val appStateHandler: AppStateHandler, private val supportedVerificationMethodsProvider: SupportedVerificationMethodsProvider, - private val features: VectorFeatures + private val features: VectorFeatures, + private val analyticsTracker: AnalyticsTracker ) : Navigator { override fun openLogin(context: Context, loginConfig: LoginConfig?, flags: Int) { @@ -148,12 +155,23 @@ class DefaultNavigator @Inject constructor( roomId: String, eventId: String?, buildTask: Boolean, - isInviteAlreadyAccepted: Boolean + isInviteAlreadyAccepted: Boolean, + trigger: ViewRoom.Trigger? ) { if (sessionHolder.getSafeActiveSession()?.getRoom(roomId) == null) { fatalError("Trying to open an unknown room $roomId", vectorPreferences.failFast()) return } + + trigger?.let { + analyticsTracker.capture( + sessionHolder.getActiveSession().getRoomSummary(roomId).toAnalyticsViewRoom( + trigger = trigger, + groupingMethod = appStateHandler.getCurrentRoomGroupingMethod() + ) + ) + } + val args = TimelineArgs(roomId = roomId, eventId = eventId, isInviteAlreadyAccepted = isInviteAlreadyAccepted) val intent = RoomDetailActivity.newIntent(context, args) startActivity(context, intent, buildTask) @@ -176,7 +194,7 @@ class DefaultNavigator @Inject constructor( Navigator.PostSwitchSpaceAction.OpenAddExistingRooms -> { startActivity(context, SpaceManageActivity.newIntent(context, spaceId, ManageType.AddRooms), false) } - Navigator.PostSwitchSpaceAction.OpenRoomList -> { + Navigator.PostSwitchSpaceAction.OpenRoomList -> { startActivity(context, SpaceExploreActivity.newIntent(context, spaceId), buildTask = false) } is Navigator.PostSwitchSpaceAction.OpenDefaultRoom -> { @@ -236,7 +254,8 @@ class DefaultNavigator @Inject constructor( val pr = session.cryptoService().verificationService().requestKeyVerification( supportedVerificationMethodsProvider.provide(), session.myUserId, - otherSessions) + otherSessions + ) VerificationBottomSheet.forSelfVerification(session, pr.transactionId ?: pr.localId) .show(context.supportFragmentManager, VerificationBottomSheet.WAITING_SELF_VERIF_TAG) } else { @@ -294,13 +313,13 @@ class DefaultNavigator @Inject constructor( context.startActivity(intent) } - override fun openMatrixToBottomSheet(context: Context, link: String) { + override fun openMatrixToBottomSheet(context: Context, link: String, origin: OriginOfMatrixTo) { if (context is AppCompatActivity) { if (context !is MatrixToBottomSheet.InteractionListener) { fatalError("Caller context should implement MatrixToBottomSheet.InteractionListener", vectorPreferences.failFast()) } // TODO check if there is already one?? - MatrixToBottomSheet.withLink(link) + MatrixToBottomSheet.withLink(link, origin) .show(context.supportFragmentManager, "HA#MatrixToBottomSheet") } } @@ -507,12 +526,14 @@ class DefaultNavigator @Inject constructor( view: View, inMemory: List, options: ((MutableList>) -> Unit)?) { - VectorAttachmentViewerActivity.newIntent(activity, + VectorAttachmentViewerActivity.newIntent( + activity, mediaData, roomId, mediaData.eventId, inMemory, - ViewCompat.getTransitionName(view)).let { intent -> + ViewCompat.getTransitionName(view) + ).let { intent -> val pairs = ArrayList>() activity.window.decorView.findViewById(android.R.id.statusBarBackground)?.let { pairs.add(Pair(it, Window.STATUS_BAR_BACKGROUND_TRANSITION_NAME)) @@ -581,23 +602,33 @@ class DefaultNavigator @Inject constructor( } override fun openThread(context: Context, threadTimelineArgs: ThreadTimelineArgs, eventIdToNavigate: String?) { - context.startActivity(ThreadsActivity.newIntent( - context = context, - threadTimelineArgs = threadTimelineArgs, - threadListArgs = null, - eventIdToNavigate = eventIdToNavigate - )) + context.startActivity( + ThreadsActivity.newIntent( + context = context, + threadTimelineArgs = threadTimelineArgs, + threadListArgs = null, + eventIdToNavigate = eventIdToNavigate + ) + ) } override fun openThreadList(context: Context, threadTimelineArgs: ThreadTimelineArgs) { - context.startActivity(ThreadsActivity.newIntent( - context = context, - threadTimelineArgs = null, - threadListArgs = ThreadListArgs( - roomId = threadTimelineArgs.roomId, - displayName = threadTimelineArgs.displayName, - avatarUrl = threadTimelineArgs.avatarUrl, - roomEncryptionTrustLevel = threadTimelineArgs.roomEncryptionTrustLevel - ))) + context.startActivity( + ThreadsActivity.newIntent( + context = context, + threadTimelineArgs = null, + threadListArgs = ThreadListArgs( + roomId = threadTimelineArgs.roomId, + displayName = threadTimelineArgs.displayName, + avatarUrl = threadTimelineArgs.avatarUrl, + roomEncryptionTrustLevel = threadTimelineArgs.roomEncryptionTrustLevel + ) + ) + ) + } + + override fun openScreenSharingPermissionDialog(screenCaptureIntent: Intent, + activityResultLauncher: ActivityResultLauncher) { + activityResultLauncher.launch(screenCaptureIntent) } } diff --git a/vector/src/main/java/im/vector/app/features/navigation/Navigator.kt b/vector/src/main/java/im/vector/app/features/navigation/Navigator.kt index 310105bd95..075b41daf3 100644 --- a/vector/src/main/java/im/vector/app/features/navigation/Navigator.kt +++ b/vector/src/main/java/im/vector/app/features/navigation/Navigator.kt @@ -23,12 +23,14 @@ import android.net.Uri import android.view.View import androidx.activity.result.ActivityResultLauncher import androidx.core.util.Pair +import im.vector.app.features.analytics.plan.ViewRoom import im.vector.app.features.crypto.recover.SetupMode import im.vector.app.features.displayname.getBestName import im.vector.app.features.home.room.threads.arguments.ThreadTimelineArgs import im.vector.app.features.location.LocationData import im.vector.app.features.location.LocationSharingMode import im.vector.app.features.login.LoginConfig +import im.vector.app.features.matrixto.OriginOfMatrixTo import im.vector.app.features.media.AttachmentData import im.vector.app.features.pin.PinMode import im.vector.app.features.poll.PollMode @@ -50,7 +52,12 @@ interface Navigator { fun softLogout(context: Context) - fun openRoom(context: Context, roomId: String, eventId: String? = null, buildTask: Boolean = false, isInviteAlreadyAccepted: Boolean = false) + fun openRoom(context: Context, + roomId: String, + eventId: String? = null, + buildTask: Boolean = false, + isInviteAlreadyAccepted: Boolean = false, + trigger: ViewRoom.Trigger? = null) sealed class PostSwitchSpaceAction { object None : PostSwitchSpaceAction() @@ -79,7 +86,7 @@ interface Navigator { fun openRoomPreview(context: Context, roomPreviewData: RoomPreviewData, fromEmailInviteLink: PermalinkData.RoomEmailInviteLink? = null) - fun openMatrixToBottomSheet(context: Context, link: String) + fun openMatrixToBottomSheet(context: Context, link: String, origin: OriginOfMatrixTo) fun openCreateRoom(context: Context, initialName: String = "", openAfterCreate: Boolean = true) @@ -168,4 +175,9 @@ interface Navigator { fun openThread(context: Context, threadTimelineArgs: ThreadTimelineArgs, eventIdToNavigate: String? = null) fun openThreadList(context: Context, threadTimelineArgs: ThreadTimelineArgs) + + fun openScreenSharingPermissionDialog( + screenCaptureIntent: Intent, + activityResultLauncher: ActivityResultLauncher + ) } diff --git a/vector/src/main/java/im/vector/app/features/notifications/CircularCache.kt b/vector/src/main/java/im/vector/app/features/notifications/CircularCache.kt index 1ac66d0ae8..5c751e0d55 100644 --- a/vector/src/main/java/im/vector/app/features/notifications/CircularCache.kt +++ b/vector/src/main/java/im/vector/app/features/notifications/CircularCache.kt @@ -17,8 +17,8 @@ package im.vector.app.features.notifications /** - * A FIFO circular buffer of T - * This class is not thread safe + * A FIFO circular buffer of T. + * This class is not thread safe. */ class CircularCache(cacheSize: Int, factory: (Int) -> Array) { diff --git a/vector/src/main/java/im/vector/app/features/notifications/InviteNotifiableEvent.kt b/vector/src/main/java/im/vector/app/features/notifications/InviteNotifiableEvent.kt index 832f97bc4e..eb0735f2be 100644 --- a/vector/src/main/java/im/vector/app/features/notifications/InviteNotifiableEvent.kt +++ b/vector/src/main/java/im/vector/app/features/notifications/InviteNotifiableEvent.kt @@ -28,5 +28,6 @@ data class InviteNotifiableEvent( val type: String?, val timestamp: Long, val soundName: String?, - override val isRedacted: Boolean = false + override val isRedacted: Boolean = false, + override val isUpdated: Boolean = false ) : NotifiableEvent diff --git a/vector/src/main/java/im/vector/app/features/notifications/NotifiableEvent.kt b/vector/src/main/java/im/vector/app/features/notifications/NotifiableEvent.kt index 52d8119cbb..0f33c525e5 100644 --- a/vector/src/main/java/im/vector/app/features/notifications/NotifiableEvent.kt +++ b/vector/src/main/java/im/vector/app/features/notifications/NotifiableEvent.kt @@ -18,7 +18,7 @@ package im.vector.app.features.notifications import java.io.Serializable /** - * Parent interface for all events which can be displayed as a Notification + * Parent interface for all events which can be displayed as a Notification. */ sealed interface NotifiableEvent : Serializable { val eventId: String @@ -27,4 +27,5 @@ sealed interface NotifiableEvent : Serializable { // Used to know if event should be replaced with the one coming from eventstream val canBeReplaced: Boolean val isRedacted: Boolean + val isUpdated: Boolean } diff --git a/vector/src/main/java/im/vector/app/features/notifications/NotifiableEventResolver.kt b/vector/src/main/java/im/vector/app/features/notifications/NotifiableEventResolver.kt index 855774e512..b9d9261abe 100644 --- a/vector/src/main/java/im/vector/app/features/notifications/NotifiableEventResolver.kt +++ b/vector/src/main/java/im/vector/app/features/notifications/NotifiableEventResolver.kt @@ -20,6 +20,7 @@ import im.vector.app.BuildConfig import im.vector.app.R import im.vector.app.core.extensions.takeAs import im.vector.app.core.resources.StringProvider +import im.vector.app.core.time.Clock import im.vector.app.features.displayname.getBestName import im.vector.app.features.home.room.detail.timeline.format.DisplayableEventFormatter import im.vector.app.features.home.room.detail.timeline.format.NoticeEventFormatter @@ -34,6 +35,10 @@ import org.matrix.android.sdk.api.session.events.model.isEdition import org.matrix.android.sdk.api.session.events.model.isImageMessage import org.matrix.android.sdk.api.session.events.model.supportsNotification import org.matrix.android.sdk.api.session.events.model.toModel +import org.matrix.android.sdk.api.session.getRoom +import org.matrix.android.sdk.api.session.getRoomSummary +import org.matrix.android.sdk.api.session.getUser +import org.matrix.android.sdk.api.session.room.getTimelineEvent import org.matrix.android.sdk.api.session.room.model.Membership import org.matrix.android.sdk.api.session.room.model.RoomMemberContent import org.matrix.android.sdk.api.session.room.model.message.MessageWithAttachmentContent @@ -55,7 +60,8 @@ import javax.inject.Inject class NotifiableEventResolver @Inject constructor( private val stringProvider: StringProvider, private val noticeEventFormatter: NoticeEventFormatter, - private val displayableEventFormatter: DisplayableEventFormatter + private val displayableEventFormatter: DisplayableEventFormatter, + private val clock: Clock, ) { // private val eventDisplay = RiotEventDisplay(context) @@ -83,7 +89,7 @@ class NotifiableEventResolver @Inject constructor( eventId = event.eventId!!, editedEventId = timelineEvent.getEditedEventId(), noisy = false, // will be updated - timestamp = event.originServerTs ?: System.currentTimeMillis(), + timestamp = event.originServerTs ?: clock.epochMillis(), description = bodyPreview, title = stringProvider.getString(R.string.notification_unknown_new_event), soundName = null, @@ -100,7 +106,7 @@ class NotifiableEventResolver @Inject constructor( // Ignore message edition if (event.isEdition()) return null - val actions = session.getActions(event) + val actions = session.pushRuleService().getActions(event) val notificationAction = actions.toNotificationAction() return if (notificationAction.shouldNotify) { @@ -175,15 +181,19 @@ class NotifiableEventResolver @Inject constructor( roomName = roomName, roomIsDirect = room.roomSummary()?.isDirect ?: false, roomAvatarPath = session.contentUrlResolver() - .resolveThumbnail(room.roomSummary()?.avatarUrl, + .resolveThumbnail( + room.roomSummary()?.avatarUrl, 250, 250, - ContentUrlResolver.ThumbnailMethod.SCALE), + ContentUrlResolver.ThumbnailMethod.SCALE + ), senderAvatarPath = session.contentUrlResolver() - .resolveThumbnail(event.senderInfo.avatarUrl, + .resolveThumbnail( + event.senderInfo.avatarUrl, 250, 250, - ContentUrlResolver.ThumbnailMethod.SCALE), + ContentUrlResolver.ThumbnailMethod.SCALE + ), matrixID = session.myUserId, soundName = null ) @@ -205,7 +215,7 @@ class NotifiableEventResolver @Inject constructor( keysClaimed = result.claimedEd25519Key?.let { mapOf("ed25519" to it) }, forwardingCurve25519KeyChain = result.forwardingCurve25519KeyChain ) - } catch (e: MXCryptoError) { + } catch (ignore: MXCryptoError) { } } } @@ -233,7 +243,7 @@ class NotifiableEventResolver @Inject constructor( private fun resolveStateRoomEvent(event: Event, session: Session, canBeReplaced: Boolean, isNoisy: Boolean): NotifiableEvent? { val content = event.content?.toModel() ?: return null val roomId = event.roomId ?: return null - val dName = event.senderId?.let { session.getRoomMember(it, roomId)?.displayName } + val dName = event.senderId?.let { session.roomService().getRoomMember(it, roomId)?.displayName } if (Membership.INVITE == content.membership) { val roomSummary = session.getRoomSummary(roomId) val body = noticeEventFormatter.format(event, dName, isDm = roomSummary?.isDirect.orFalse()) diff --git a/vector/src/main/java/im/vector/app/features/notifications/NotifiableMessageEvent.kt b/vector/src/main/java/im/vector/app/features/notifications/NotifiableMessageEvent.kt index 35718666b0..d13e41daa8 100644 --- a/vector/src/main/java/im/vector/app/features/notifications/NotifiableMessageEvent.kt +++ b/vector/src/main/java/im/vector/app/features/notifications/NotifiableMessageEvent.kt @@ -38,7 +38,8 @@ data class NotifiableMessageEvent( // This is used for >N notification, as the result of a smart reply val outGoingMessage: Boolean = false, val outGoingMessageFailed: Boolean = false, - override val isRedacted: Boolean = false + override val isRedacted: Boolean = false, + override val isUpdated: Boolean = false ) : NotifiableEvent { val type: String = EventType.MESSAGE diff --git a/vector/src/main/java/im/vector/app/features/notifications/NotificationAction.kt b/vector/src/main/java/im/vector/app/features/notifications/NotificationAction.kt index c11ce6794f..86a9e917e5 100644 --- a/vector/src/main/java/im/vector/app/features/notifications/NotificationAction.kt +++ b/vector/src/main/java/im/vector/app/features/notifications/NotificationAction.kt @@ -15,7 +15,7 @@ */ package im.vector.app.features.notifications -import org.matrix.android.sdk.api.pushrules.Action +import org.matrix.android.sdk.api.session.pushrules.Action data class NotificationAction( val shouldNotify: Boolean, diff --git a/vector/src/main/java/im/vector/app/features/notifications/NotificationBitmapLoader.kt b/vector/src/main/java/im/vector/app/features/notifications/NotificationBitmapLoader.kt index 518b011ffd..c289d79ee8 100644 --- a/vector/src/main/java/im/vector/app/features/notifications/NotificationBitmapLoader.kt +++ b/vector/src/main/java/im/vector/app/features/notifications/NotificationBitmapLoader.kt @@ -33,7 +33,7 @@ import javax.inject.Singleton class NotificationBitmapLoader @Inject constructor(private val context: Context) { /** - * Get icon of a room + * Get icon of a room. */ @WorkerThread fun getRoomBitmap(path: String?): Bitmap? { diff --git a/vector/src/main/java/im/vector/app/features/notifications/NotificationBroadcastReceiver.kt b/vector/src/main/java/im/vector/app/features/notifications/NotificationBroadcastReceiver.kt index 505f4cc4a0..a4022f75c8 100644 --- a/vector/src/main/java/im/vector/app/features/notifications/NotificationBroadcastReceiver.kt +++ b/vector/src/main/java/im/vector/app/features/notifications/NotificationBroadcastReceiver.kt @@ -23,12 +23,15 @@ import androidx.core.app.RemoteInput import dagger.hilt.android.AndroidEntryPoint import im.vector.app.R import im.vector.app.core.di.ActiveSessionHolder +import im.vector.app.core.time.Clock import im.vector.app.features.analytics.AnalyticsTracker import im.vector.app.features.analytics.extensions.toAnalyticsJoinedRoom +import im.vector.app.features.analytics.plan.JoinedRoom import im.vector.app.features.session.coroutineScope import kotlinx.coroutines.launch import org.matrix.android.sdk.api.extensions.tryOrNull import org.matrix.android.sdk.api.session.Session +import org.matrix.android.sdk.api.session.getRoom import org.matrix.android.sdk.api.session.room.Room import org.matrix.android.sdk.api.session.room.read.ReadService import timber.log.Timber @@ -36,7 +39,7 @@ import java.util.UUID import javax.inject.Inject /** - * Receives actions broadcast by notification (on click, on dismiss, inline replies, etc.) + * Receives actions broadcast by notification (on click, on dismiss, inline replies, etc.). */ @AndroidEntryPoint class NotificationBroadcastReceiver : BroadcastReceiver() { @@ -44,6 +47,7 @@ class NotificationBroadcastReceiver : BroadcastReceiver() { @Inject lateinit var notificationDrawerManager: NotificationDrawerManager @Inject lateinit var activeSessionHolder: ActiveSessionHolder @Inject lateinit var analyticsTracker: AnalyticsTracker + @Inject lateinit var clock: Clock override fun onReceive(context: Context?, intent: Intent?) { if (intent == null || context == null) return @@ -83,8 +87,8 @@ class NotificationBroadcastReceiver : BroadcastReceiver() { if (room != null) { session.coroutineScope.launch { tryOrNull { - session.joinRoom(room.roomId) - analyticsTracker.capture(room.roomSummary().toAnalyticsJoinedRoom()) + session.roomService().joinRoom(room.roomId) + analyticsTracker.capture(room.roomSummary().toAnalyticsJoinedRoom(JoinedRoom.Trigger.Notification)) } } } @@ -94,7 +98,7 @@ class NotificationBroadcastReceiver : BroadcastReceiver() { private fun handleRejectRoom(roomId: String) { activeSessionHolder.getSafeActiveSession()?.let { session -> session.coroutineScope.launch { - tryOrNull { session.leaveRoom(roomId) } + tryOrNull { session.roomService().leaveRoom(roomId) } } } } @@ -104,7 +108,7 @@ class NotificationBroadcastReceiver : BroadcastReceiver() { val room = session.getRoom(roomId) if (room != null) { session.coroutineScope.launch { - tryOrNull { room.markAsRead(ReadService.MarkAsReadParams.READ_RECEIPT) } + tryOrNull { room.readService().markAsRead(ReadService.MarkAsReadParams.READ_RECEIPT) } } } } @@ -127,7 +131,7 @@ class NotificationBroadcastReceiver : BroadcastReceiver() { } private fun sendMatrixEvent(message: String, session: Session, room: Room, context: Context?) { - room.sendTextMessage(message) + room.sendService().sendTextMessage(message) // Create a new event to be displayed in the notification drawer, right now @@ -136,8 +140,8 @@ class NotificationBroadcastReceiver : BroadcastReceiver() { eventId = UUID.randomUUID().toString(), editedEventId = null, noisy = false, - timestamp = System.currentTimeMillis(), - senderName = session.getRoomMember(session.myUserId, room.roomId)?.displayName + timestamp = clock.epochMillis(), + senderName = session.roomService().getRoomMember(session.myUserId, room.roomId)?.displayName ?: context?.getString(R.string.notification_sender_me), senderId = session.myUserId, body = message, @@ -187,7 +191,7 @@ class NotificationBroadcastReceiver : BroadcastReceiver() { val notifiableMessageEvent = NotifiableMessageEvent( event.eventId, false, - System.currentTimeMillis(), + clock.epochMillis(), session.myUser?.displayname ?: context?.getString(R.string.notification_sender_me), session.myUserId, diff --git a/vector/src/main/java/im/vector/app/features/notifications/NotificationDrawerManager.kt b/vector/src/main/java/im/vector/app/features/notifications/NotificationDrawerManager.kt index fa56e3b8ed..5819e02897 100644 --- a/vector/src/main/java/im/vector/app/features/notifications/NotificationDrawerManager.kt +++ b/vector/src/main/java/im/vector/app/features/notifications/NotificationDrawerManager.kt @@ -27,6 +27,7 @@ import im.vector.app.features.displayname.getBestName import im.vector.app.features.settings.VectorPreferences import org.matrix.android.sdk.api.session.Session import org.matrix.android.sdk.api.session.content.ContentUrlResolver +import org.matrix.android.sdk.api.session.getUser import org.matrix.android.sdk.api.util.toMatrixItem import timber.log.Timber import javax.inject.Inject @@ -56,7 +57,7 @@ class NotificationDrawerManager @Inject constructor( get() = activeSessionDataSource.currentValue?.orNull() /** - * Lazily initializes the NotificationState as we rely on having a current session in order to fetch the persisted queue of events + * Lazily initializes the NotificationState as we rely on having a current session in order to fetch the persisted queue of events. */ private val notificationState by lazy { createInitialNotificationState() } private val avatarSize = context.resources.getDimensionPixelSize(R.dimen.profile_avatar_size) @@ -101,7 +102,7 @@ class NotificationDrawerManager @Inject constructor( } /** - * Clear all known events and refresh the notification drawer + * Clear all known events and refresh the notification drawer. */ fun clearAllEvents() { updateEvents { it.clear() } @@ -154,7 +155,8 @@ class NotificationDrawerManager @Inject constructor( Timber.w(throwable, "refreshNotificationDrawerBg failure") } }, - canHandle.waitMillis()) + canHandle.waitMillis() + ) } @WorkerThread diff --git a/vector/src/main/java/im/vector/app/features/notifications/NotificationEventPersistence.kt b/vector/src/main/java/im/vector/app/features/notifications/NotificationEventPersistence.kt index a4a7570fec..5a716ff173 100644 --- a/vector/src/main/java/im/vector/app/features/notifications/NotificationEventPersistence.kt +++ b/vector/src/main/java/im/vector/app/features/notifications/NotificationEventPersistence.kt @@ -34,7 +34,7 @@ class NotificationEventPersistence @Inject constructor(private val context: Cont val file = File(context.applicationContext.cacheDir, ROOMS_NOTIFICATIONS_FILE_NAME) if (file.exists()) { file.inputStream().use { - val events: ArrayList? = currentSession?.loadSecureSecret(it, KEY_ALIAS_SECRET_STORAGE) + val events: ArrayList? = currentSession?.secureStorageService()?.loadSecureSecret(it, KEY_ALIAS_SECRET_STORAGE) if (events != null) { return factory(events) } @@ -55,7 +55,7 @@ class NotificationEventPersistence @Inject constructor(private val context: Cont val file = File(context.applicationContext.cacheDir, ROOMS_NOTIFICATIONS_FILE_NAME) if (!file.exists()) file.createNewFile() FileOutputStream(file).use { - currentSession.securelyStoreObject(queuedEvents.rawEvents(), KEY_ALIAS_SECRET_STORAGE, it) + currentSession.secureStorageService().securelyStoreObject(queuedEvents.rawEvents(), KEY_ALIAS_SECRET_STORAGE, it) } } catch (e: Throwable) { Timber.e(e, "## Failed to save cached notification info") diff --git a/vector/src/main/java/im/vector/app/features/notifications/NotificationEventQueue.kt b/vector/src/main/java/im/vector/app/features/notifications/NotificationEventQueue.kt index 04506b218b..7c933c389d 100644 --- a/vector/src/main/java/im/vector/app/features/notifications/NotificationEventQueue.kt +++ b/vector/src/main/java/im/vector/app/features/notifications/NotificationEventQueue.kt @@ -112,7 +112,13 @@ data class NotificationEventQueue( private fun replace(replace: NotifiableEvent, with: NotifiableEvent) { queue.remove(replace) - queue.add(with) + queue.add( + when (with) { + is InviteNotifiableEvent -> with.copy(isUpdated = true) + is NotifiableMessageEvent -> with.copy(isUpdated = true) + is SimpleNotifiableEvent -> with.copy(isUpdated = true) + } + ) } fun clearMemberShipNotificationForRoom(roomId: String) { diff --git a/vector/src/main/java/im/vector/app/features/notifications/NotificationFactory.kt b/vector/src/main/java/im/vector/app/features/notifications/NotificationFactory.kt index b95bbe1bf5..4b6815e7e4 100644 --- a/vector/src/main/java/im/vector/app/features/notifications/NotificationFactory.kt +++ b/vector/src/main/java/im/vector/app/features/notifications/NotificationFactory.kt @@ -96,7 +96,8 @@ class NotificationFactory @Inject constructor( invitationNotifications = invitationMeta, simpleNotifications = simpleMeta, useCompleteNotificationFormat = useCompleteNotificationFormat - )) + ) + ) } } } diff --git a/vector/src/main/java/im/vector/app/features/notifications/NotificationState.kt b/vector/src/main/java/im/vector/app/features/notifications/NotificationState.kt index 193116a6bc..3114cd45ca 100644 --- a/vector/src/main/java/im/vector/app/features/notifications/NotificationState.kt +++ b/vector/src/main/java/im/vector/app/features/notifications/NotificationState.kt @@ -18,19 +18,19 @@ package im.vector.app.features.notifications class NotificationState( /** - * The notifiable events queued for rendering or currently rendered + * The notifiable events queued for rendering or currently rendered. * - * this is our source of truth for notifications, any changes to this list will be rendered as notifications - * when events are removed the previously rendered notifications will be cancelled - * when adding or updating, the notifications will be notified + * This is our source of truth for notifications, any changes to this list will be rendered as notifications. + * When events are removed the previously rendered notifications will be cancelled. + * When adding or updating, the notifications will be notified. * - * Events are unique by their properties, we should be careful not to insert multiple events with the same event-id + * Events are unique by their properties, we should be careful not to insert multiple events with the same event-id. */ private val queuedEvents: NotificationEventQueue, /** - * The last known rendered notifiable events - * we keep track of them in order to know which events have been removed from the eventList + * The last known rendered notifiable events. + * We keep track of them in order to know which events have been removed from the eventList * allowing us to cancel any notifications previous displayed by now removed events */ private val renderedEvents: MutableList>, diff --git a/vector/src/main/java/im/vector/app/features/notifications/NotificationUtils.kt b/vector/src/main/java/im/vector/app/features/notifications/NotificationUtils.kt index 161b58d53d..d03fcadcfa 100755 --- a/vector/src/main/java/im/vector/app/features/notifications/NotificationUtils.kt +++ b/vector/src/main/java/im/vector/app/features/notifications/NotificationUtils.kt @@ -51,6 +51,7 @@ import im.vector.app.core.extensions.createIgnoredUri import im.vector.app.core.platform.PendingIntentCompat import im.vector.app.core.resources.StringProvider import im.vector.app.core.services.CallService +import im.vector.app.core.time.Clock import im.vector.app.core.utils.startNotificationChannelSettingsIntent import im.vector.app.features.call.VectorCallActivity import im.vector.app.features.call.service.CallHeadsUpActionReceiver @@ -72,9 +73,12 @@ import kotlin.random.Random * Note: Cannot inject ColorProvider in the constructor, because it requires an Activity */ @Singleton -class NotificationUtils @Inject constructor(private val context: Context, - private val stringProvider: StringProvider, - private val vectorPreferences: VectorPreferences) { +class NotificationUtils @Inject constructor( + private val context: Context, + private val stringProvider: StringProvider, + private val vectorPreferences: VectorPreferences, + private val clock: Clock, +) { companion object { /* ========================================================================================== @@ -170,9 +174,11 @@ class NotificationUtils @Inject constructor(private val context: Context, * Default notification importance: shows everywhere, makes noise, but does not visually * intrude. */ - notificationManager.createNotificationChannel(NotificationChannel(NOISY_NOTIFICATION_CHANNEL_ID, + notificationManager.createNotificationChannel(NotificationChannel( + NOISY_NOTIFICATION_CHANNEL_ID, stringProvider.getString(R.string.notification_noisy_notifications).ifEmpty { "Noisy notifications" }, - NotificationManager.IMPORTANCE_DEFAULT) + NotificationManager.IMPORTANCE_DEFAULT + ) .apply { description = stringProvider.getString(R.string.notification_noisy_notifications) enableVibration(true) @@ -183,9 +189,11 @@ class NotificationUtils @Inject constructor(private val context: Context, /** * Low notification importance: shows everywhere, but is not intrusive. */ - notificationManager.createNotificationChannel(NotificationChannel(SILENT_NOTIFICATION_CHANNEL_ID, + notificationManager.createNotificationChannel(NotificationChannel( + SILENT_NOTIFICATION_CHANNEL_ID, stringProvider.getString(R.string.notification_silent_notifications).ifEmpty { "Silent notifications" }, - NotificationManager.IMPORTANCE_LOW) + NotificationManager.IMPORTANCE_LOW + ) .apply { description = stringProvider.getString(R.string.notification_silent_notifications) setSound(null, null) @@ -193,18 +201,22 @@ class NotificationUtils @Inject constructor(private val context: Context, lightColor = accentColor }) - notificationManager.createNotificationChannel(NotificationChannel(LISTENING_FOR_EVENTS_NOTIFICATION_CHANNEL_ID, + notificationManager.createNotificationChannel(NotificationChannel( + LISTENING_FOR_EVENTS_NOTIFICATION_CHANNEL_ID, stringProvider.getString(R.string.notification_listening_for_events).ifEmpty { "Listening for events" }, - NotificationManager.IMPORTANCE_MIN) + NotificationManager.IMPORTANCE_MIN + ) .apply { description = stringProvider.getString(R.string.notification_listening_for_events) setSound(null, null) setShowBadge(false) }) - notificationManager.createNotificationChannel(NotificationChannel(CALL_NOTIFICATION_CHANNEL_ID, + notificationManager.createNotificationChannel(NotificationChannel( + CALL_NOTIFICATION_CHANNEL_ID, stringProvider.getString(R.string.call).ifEmpty { "Call" }, - NotificationManager.IMPORTANCE_HIGH) + NotificationManager.IMPORTANCE_HIGH + ) .apply { description = stringProvider.getString(R.string.call) setSound(null, null) @@ -218,7 +230,7 @@ class NotificationUtils @Inject constructor(private val context: Context, } /** - * Build a polling thread listener notification + * Build a polling thread listener notification. * * @param subTitleResId subtitle string resource Id of the notification * @return the polling thread listener notification @@ -262,11 +274,13 @@ class NotificationUtils @Inject constructor(private val context: Context, // reflection at runtime, to avoid compiler error: "Cannot resolve method.." try { val deprecatedMethod = notification.javaClass - .getMethod("setLatestEventInfo", + .getMethod( + "setLatestEventInfo", Context::class.java, CharSequence::class.java, CharSequence::class.java, - PendingIntent::class.java) + PendingIntent::class.java + ) deprecatedMethod.invoke(notification, context, stringProvider.getString(R.string.app_name), stringProvider.getString(subTitleResId), pi) } catch (ex: Exception) { Timber.e(ex, "## buildNotification(): Exception - setLatestEventInfo() Msg=") @@ -323,7 +337,7 @@ class NotificationUtils @Inject constructor(private val context: Context, } val contentPendingIntent = PendingIntent.getActivity( context, - System.currentTimeMillis().toInt(), + clock.epochMillis().toInt(), contentIntent, PendingIntentCompat.FLAG_IMMUTABLE ) @@ -337,7 +351,7 @@ class NotificationUtils @Inject constructor(private val context: Context, mode = VectorCallActivity.INCOMING_ACCEPT ) ) - .getPendingIntent(System.currentTimeMillis().toInt(), PendingIntent.FLAG_UPDATE_CURRENT or PendingIntentCompat.FLAG_IMMUTABLE) + .getPendingIntent(clock.epochMillis().toInt(), PendingIntent.FLAG_UPDATE_CURRENT or PendingIntentCompat.FLAG_IMMUTABLE) val rejectCallPendingIntent = buildRejectCallPendingIntent(call.callId) @@ -386,13 +400,14 @@ class NotificationUtils @Inject constructor(private val context: Context, val contentIntent = VectorCallActivity.newIntent( context = context, call = call, - mode = null).apply { + mode = null + ).apply { flags = Intent.FLAG_ACTIVITY_CLEAR_TOP or Intent.FLAG_ACTIVITY_SINGLE_TOP data = createIgnoredUri(call.callId) } val contentPendingIntent = PendingIntent.getActivity( context, - System.currentTimeMillis().toInt(), + clock.epochMillis().toInt(), contentIntent, PendingIntentCompat.FLAG_IMMUTABLE ) @@ -413,7 +428,7 @@ class NotificationUtils @Inject constructor(private val context: Context, } /** - * Build a pending call notification + * Build a pending call notification. * * @param isVideo true if this is a video call, false for voice call * @param roomName the room name in which the call is pending. @@ -453,7 +468,7 @@ class NotificationUtils @Inject constructor(private val context: Context, val contentPendingIntent = TaskStackBuilder.create(context) .addNextIntentWithParentStack(HomeActivity.newIntent(context)) .addNextIntent(VectorCallActivity.newIntent(context, call, null)) - .getPendingIntent(System.currentTimeMillis().toInt(), PendingIntent.FLAG_UPDATE_CURRENT or PendingIntentCompat.FLAG_IMMUTABLE) + .getPendingIntent(clock.epochMillis().toInt(), PendingIntent.FLAG_UPDATE_CURRENT or PendingIntentCompat.FLAG_IMMUTABLE) builder.setContentIntent(contentPendingIntent) @@ -467,14 +482,14 @@ class NotificationUtils @Inject constructor(private val context: Context, } return PendingIntent.getBroadcast( context, - System.currentTimeMillis().toInt(), + clock.epochMillis().toInt(), rejectCallActionReceiver, PendingIntent.FLAG_UPDATE_CURRENT or PendingIntentCompat.FLAG_IMMUTABLE ) } /** - * Build a temporary (because service will be stopped just after) notification for the CallService, when a call is ended + * Build a temporary (because service will be stopped just after) notification for the CallService, when a call is ended. */ fun buildCallEndedNotification(isVideoCall: Boolean): Notification { return NotificationCompat.Builder(context, SILENT_NOTIFICATION_CHANNEL_ID) @@ -493,7 +508,7 @@ class NotificationUtils @Inject constructor(private val context: Context, } /** - * Build notification for the CallService, when a call is missed + * Build notification for the CallService, when a call is missed. */ fun buildCallMissedNotification(callInformation: CallService.CallInformation): Notification { val builder = NotificationCompat.Builder(context, SILENT_NOTIFICATION_CHANNEL_ID) @@ -515,7 +530,7 @@ class NotificationUtils @Inject constructor(private val context: Context, val contentPendingIntent = TaskStackBuilder.create(context) .addNextIntentWithParentStack(HomeActivity.newIntent(context)) .addNextIntent(RoomDetailActivity.newIntent(context, TimelineArgs(callInformation.nativeRoomId))) - .getPendingIntent(System.currentTimeMillis().toInt(), PendingIntent.FLAG_UPDATE_CURRENT or PendingIntentCompat.FLAG_IMMUTABLE) + .getPendingIntent(clock.epochMillis().toInt(), PendingIntent.FLAG_UPDATE_CURRENT or PendingIntentCompat.FLAG_IMMUTABLE) builder.setContentIntent(contentPendingIntent) return builder.build() @@ -535,6 +550,20 @@ class NotificationUtils @Inject constructor(private val context: Context, .build() } + /** + * Creates a notification that indicates the application is capturing the screen. + */ + fun buildScreenSharingNotification(): Notification { + return NotificationCompat.Builder(context, SILENT_NOTIFICATION_CHANNEL_ID) + .setContentTitle(stringProvider.getString(R.string.screen_sharing_notification_title)) + .setContentText(stringProvider.getString(R.string.screen_sharing_notification_description)) + .setSmallIcon(R.drawable.ic_share_screen) + .setColor(ThemeUtils.getColor(context, android.R.attr.colorPrimary)) + .setCategory(NotificationCompat.CATEGORY_SERVICE) + .setContentIntent(buildOpenHomePendingIntentForSummary()) + .build() + } + fun buildDownloadFileNotification(uri: Uri, fileName: String, mimeType: String): Notification { return NotificationCompat.Builder(context, SILENT_NOTIFICATION_CHANNEL_ID) .setGroup(stringProvider.getString(R.string.app_name)) @@ -548,7 +577,7 @@ class NotificationUtils @Inject constructor(private val context: Context, } PendingIntent.getActivity( context, - System.currentTimeMillis().toInt(), + clock.epochMillis().toInt(), intent, PendingIntent.FLAG_UPDATE_CURRENT or PendingIntentCompat.FLAG_IMMUTABLE ).let { @@ -559,7 +588,7 @@ class NotificationUtils @Inject constructor(private val context: Context, } /** - * Build a notification for a Room + * Build a notification for a Room. */ fun buildMessagesListNotification(messageStyle: NotificationCompat.MessagingStyle, roomInfo: RoomEventGroupInfo, @@ -574,7 +603,7 @@ class NotificationUtils @Inject constructor(private val context: Context, val channelID = if (roomInfo.shouldBing) NOISY_NOTIFICATION_CHANNEL_ID else SILENT_NOTIFICATION_CHANNEL_ID return NotificationCompat.Builder(context, channelID) - .setOnlyAlertOnce(true) + .setOnlyAlertOnce(roomInfo.isUpdated) .setWhen(lastMessageTimestamp) // MESSAGING_STYLE sets title and content for API 16 and above devices. .setStyle(messageStyle) @@ -622,13 +651,15 @@ class NotificationUtils @Inject constructor(private val context: Context, markRoomReadIntent.putExtra(NotificationBroadcastReceiver.KEY_ROOM_ID, roomInfo.roomId) val markRoomReadPendingIntent = PendingIntent.getBroadcast( context, - System.currentTimeMillis().toInt(), + clock.epochMillis().toInt(), markRoomReadIntent, PendingIntent.FLAG_UPDATE_CURRENT or PendingIntentCompat.FLAG_IMMUTABLE ) - NotificationCompat.Action.Builder(R.drawable.ic_material_done_all_white, - stringProvider.getString(R.string.action_mark_room_read), markRoomReadPendingIntent) + NotificationCompat.Action.Builder( + R.drawable.ic_material_done_all_white, + stringProvider.getString(R.string.action_mark_room_read), markRoomReadPendingIntent + ) .setSemanticAction(NotificationCompat.Action.SEMANTIC_ACTION_MARK_AS_READ) .setShowsUserInterface(false) .build() @@ -640,8 +671,10 @@ class NotificationUtils @Inject constructor(private val context: Context, val remoteInput = RemoteInput.Builder(NotificationBroadcastReceiver.KEY_TEXT_REPLY) .setLabel(stringProvider.getString(R.string.action_quick_reply)) .build() - NotificationCompat.Action.Builder(R.drawable.vector_notification_quick_reply, - stringProvider.getString(R.string.action_quick_reply), replyPendingIntent) + NotificationCompat.Action.Builder( + R.drawable.vector_notification_quick_reply, + stringProvider.getString(R.string.action_quick_reply), replyPendingIntent + ) .addRemoteInput(remoteInput) .setSemanticAction(NotificationCompat.Action.SEMANTIC_ACTION_REPLY) .setShowsUserInterface(false) @@ -663,7 +696,7 @@ class NotificationUtils @Inject constructor(private val context: Context, intent.action = DISMISS_ROOM_NOTIF_ACTION val pendingIntent = PendingIntent.getBroadcast( context.applicationContext, - System.currentTimeMillis().toInt(), + clock.epochMillis().toInt(), intent, PendingIntent.FLAG_UPDATE_CURRENT or PendingIntentCompat.FLAG_IMMUTABLE ) @@ -698,7 +731,7 @@ class NotificationUtils @Inject constructor(private val context: Context, rejectIntent.putExtra(NotificationBroadcastReceiver.KEY_ROOM_ID, roomId) val rejectIntentPendingIntent = PendingIntent.getBroadcast( context, - System.currentTimeMillis().toInt(), + clock.epochMillis().toInt(), rejectIntent, PendingIntent.FLAG_UPDATE_CURRENT or PendingIntentCompat.FLAG_IMMUTABLE ) @@ -716,7 +749,7 @@ class NotificationUtils @Inject constructor(private val context: Context, joinIntent.putExtra(NotificationBroadcastReceiver.KEY_ROOM_ID, roomId) val joinIntentPendingIntent = PendingIntent.getBroadcast( context, - System.currentTimeMillis().toInt(), + clock.epochMillis().toInt(), joinIntent, PendingIntent.FLAG_UPDATE_CURRENT or PendingIntentCompat.FLAG_IMMUTABLE ) @@ -797,7 +830,7 @@ class NotificationUtils @Inject constructor(private val context: Context, .addNextIntentWithParentStack(HomeActivity.newIntent(context)) .addNextIntent(roomIntentTap) .getPendingIntent( - System.currentTimeMillis().toInt(), + clock.epochMillis().toInt(), PendingIntent.FLAG_UPDATE_CURRENT or PendingIntentCompat.FLAG_IMMUTABLE ) } @@ -830,7 +863,7 @@ class NotificationUtils @Inject constructor(private val context: Context, intent.putExtra(NotificationBroadcastReceiver.KEY_ROOM_ID, roomId) return PendingIntent.getBroadcast( context, - System.currentTimeMillis().toInt(), + clock.epochMillis().toInt(), intent, // PendingIntents attached to actions with remote inputs must be mutable PendingIntent.FLAG_UPDATE_CURRENT or PendingIntentCompat.FLAG_MUTABLE @@ -856,7 +889,7 @@ class NotificationUtils @Inject constructor(private val context: Context, // // Number of new notifications for API <24 (M and below) devices. /** - * Build the summary notification + * Build the summary notification. */ fun buildSummaryListNotification(style: NotificationCompat.InboxStyle?, compatSummary: String, @@ -918,14 +951,14 @@ class NotificationUtils @Inject constructor(private val context: Context, } /** - * Cancel the foreground notification service + * Cancel the foreground notification service. */ fun cancelNotificationForegroundService() { notificationManager.cancel(NOTIFICATION_ID_FOREGROUND_SERVICE) } /** - * Cancel all the notification + * Cancel all the notification. */ fun cancelAllNotifications() { // Keep this try catch (reported by GA) @@ -974,7 +1007,7 @@ class NotificationUtils @Inject constructor(private val context: Context, } /** - * Return true it the user has enabled the do not disturb mode + * Return true it the user has enabled the do not disturb mode. */ fun isDoNotDisturbModeOn(): Boolean { if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) { diff --git a/vector/src/main/java/im/vector/app/features/notifications/OutdatedEventDetector.kt b/vector/src/main/java/im/vector/app/features/notifications/OutdatedEventDetector.kt index c6ef616afe..bb45d74708 100644 --- a/vector/src/main/java/im/vector/app/features/notifications/OutdatedEventDetector.kt +++ b/vector/src/main/java/im/vector/app/features/notifications/OutdatedEventDetector.kt @@ -16,6 +16,7 @@ package im.vector.app.features.notifications import im.vector.app.ActiveSessionDataSource +import org.matrix.android.sdk.api.session.getRoom import javax.inject.Inject class OutdatedEventDetector @Inject constructor( @@ -34,7 +35,7 @@ class OutdatedEventDetector @Inject constructor( val eventID = notifiableEvent.eventId val roomID = notifiableEvent.roomId val room = session.getRoom(roomID) ?: return false - return room.isEventRead(eventID) + return room.readService().isEventRead(eventID) } return false } diff --git a/vector/src/main/java/im/vector/app/features/notifications/PushRuleTriggerListener.kt b/vector/src/main/java/im/vector/app/features/notifications/PushRuleTriggerListener.kt index cd08820fc1..58f895bf0c 100644 --- a/vector/src/main/java/im/vector/app/features/notifications/PushRuleTriggerListener.kt +++ b/vector/src/main/java/im/vector/app/features/notifications/PushRuleTriggerListener.kt @@ -21,10 +21,10 @@ import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.SupervisorJob import kotlinx.coroutines.cancelChildren import kotlinx.coroutines.launch -import org.matrix.android.sdk.api.pushrules.PushEvents -import org.matrix.android.sdk.api.pushrules.PushRuleService -import org.matrix.android.sdk.api.pushrules.getActions import org.matrix.android.sdk.api.session.Session +import org.matrix.android.sdk.api.session.pushrules.PushEvents +import org.matrix.android.sdk.api.session.pushrules.PushRuleService +import org.matrix.android.sdk.api.session.pushrules.getActions import timber.log.Timber import javax.inject.Inject import javax.inject.Singleton @@ -71,12 +71,12 @@ class PushRuleTriggerListener @Inject constructor( stop() } this.session = session - session.addPushRuleListener(this) + session.pushRuleService().addPushRuleListener(this) } fun stop() { scope.coroutineContext.cancelChildren(CancellationException("PushRuleTriggerListener stopping")) - session?.removePushRuleListener(this) + session?.pushRuleService()?.removePushRuleListener(this) session = null notificationDrawerManager.clearAllEvents() } diff --git a/vector/src/main/java/im/vector/app/features/notifications/RoomEventGroupInfo.kt b/vector/src/main/java/im/vector/app/features/notifications/RoomEventGroupInfo.kt index 64e40ed748..fa1df1e753 100644 --- a/vector/src/main/java/im/vector/app/features/notifications/RoomEventGroupInfo.kt +++ b/vector/src/main/java/im/vector/app/features/notifications/RoomEventGroupInfo.kt @@ -17,7 +17,7 @@ package im.vector.app.features.notifications /** - * Data class to hold information about a group of notifications for a room + * Data class to hold information about a group of notifications for a room. */ data class RoomEventGroupInfo( val roomId: String, @@ -31,4 +31,5 @@ data class RoomEventGroupInfo( var shouldBing: Boolean = false var customSound: String? = null var hasSmartReplyError: Boolean = false + var isUpdated: Boolean = false } diff --git a/vector/src/main/java/im/vector/app/features/notifications/RoomGroupMessageCreator.kt b/vector/src/main/java/im/vector/app/features/notifications/RoomGroupMessageCreator.kt index 8310c15daa..aa54176815 100644 --- a/vector/src/main/java/im/vector/app/features/notifications/RoomGroupMessageCreator.kt +++ b/vector/src/main/java/im/vector/app/features/notifications/RoomGroupMessageCreator.kt @@ -36,11 +36,12 @@ class RoomGroupMessageCreator @Inject constructor( val firstKnownRoomEvent = events[0] val roomName = firstKnownRoomEvent.roomName ?: firstKnownRoomEvent.senderName ?: "" val roomIsGroup = !firstKnownRoomEvent.roomIsDirect - val style = NotificationCompat.MessagingStyle(Person.Builder() - .setName(userDisplayName) - .setIcon(bitmapLoader.getUserIcon(userAvatarUrl)) - .setKey(firstKnownRoomEvent.matrixID) - .build() + val style = NotificationCompat.MessagingStyle( + Person.Builder() + .setName(userDisplayName) + .setIcon(bitmapLoader.getUserIcon(userAvatarUrl)) + .setKey(firstKnownRoomEvent.matrixID) + .build() ).also { it.conversationTitle = roomName.takeIf { roomIsGroup } it.isGroupConversation = roomIsGroup @@ -72,6 +73,7 @@ class RoomGroupMessageCreator @Inject constructor( it.hasSmartReplyError = smartReplyErrors.isNotEmpty() it.shouldBing = meta.shouldBing it.customSound = events.last().soundName + it.isUpdated = events.last().isUpdated }, largeIcon = largeBitmap, lastMessageTimestamp, diff --git a/vector/src/main/java/im/vector/app/features/notifications/SimpleNotifiableEvent.kt b/vector/src/main/java/im/vector/app/features/notifications/SimpleNotifiableEvent.kt index 8c72372204..a58f22087d 100644 --- a/vector/src/main/java/im/vector/app/features/notifications/SimpleNotifiableEvent.kt +++ b/vector/src/main/java/im/vector/app/features/notifications/SimpleNotifiableEvent.kt @@ -26,5 +26,6 @@ data class SimpleNotifiableEvent( val timestamp: Long, val soundName: String?, override var canBeReplaced: Boolean, - override val isRedacted: Boolean = false + override val isRedacted: Boolean = false, + override val isUpdated: Boolean = false ) : NotifiableEvent diff --git a/vector/src/main/java/im/vector/app/features/notifications/SummaryGroupMessageCreator.kt b/vector/src/main/java/im/vector/app/features/notifications/SummaryGroupMessageCreator.kt index 91163434c2..7d1cb074ec 100644 --- a/vector/src/main/java/im/vector/app/features/notifications/SummaryGroupMessageCreator.kt +++ b/vector/src/main/java/im/vector/app/features/notifications/SummaryGroupMessageCreator.kt @@ -99,12 +99,16 @@ class SummaryGroupMessageCreator @Inject constructor( val invitationsStr = stringProvider.getQuantityString(R.plurals.notification_invitations, invitationEventsCount, invitationEventsCount) if (messageNotificationCount > 0) { // Invitation and message - val messageStr = stringProvider.getQuantityString(R.plurals.room_new_messages_notification, - messageNotificationCount, messageNotificationCount) + val messageStr = stringProvider.getQuantityString( + R.plurals.room_new_messages_notification, + messageNotificationCount, messageNotificationCount + ) if (roomCount > 1) { // In several rooms - val roomStr = stringProvider.getQuantityString(R.plurals.notification_unread_notified_messages_in_room_rooms, - roomCount, roomCount) + val roomStr = stringProvider.getQuantityString( + R.plurals.notification_unread_notified_messages_in_room_rooms, + roomCount, roomCount + ) stringProvider.getString( R.string.notification_unread_notified_messages_in_room_and_invitation, messageStr, @@ -125,8 +129,10 @@ class SummaryGroupMessageCreator @Inject constructor( } } else { // No invitation, only messages - val messageStr = stringProvider.getQuantityString(R.plurals.room_new_messages_notification, - messageNotificationCount, messageNotificationCount) + val messageStr = stringProvider.getQuantityString( + R.plurals.room_new_messages_notification, + messageNotificationCount, messageNotificationCount + ) if (roomCount > 1) { // In several rooms val roomStr = stringProvider.getQuantityString(R.plurals.notification_unread_notified_messages_in_room_rooms, roomCount, roomCount) diff --git a/vector/src/main/java/im/vector/app/features/onboarding/Login2Variant.kt b/vector/src/main/java/im/vector/app/features/onboarding/Login2Variant.kt index 163af5d8d1..e6b5cfc95c 100644 --- a/vector/src/main/java/im/vector/app/features/onboarding/Login2Variant.kt +++ b/vector/src/main/java/im/vector/app/features/onboarding/Login2Variant.kt @@ -41,7 +41,6 @@ import im.vector.app.features.login.LoginWaitForEmailFragmentArgument import im.vector.app.features.login.TextInputFormFragmentMode import im.vector.app.features.login.isSupported import im.vector.app.features.login.terms.LoginTermsFragmentArgument -import im.vector.app.features.login.terms.toLocalizedLoginTerms import im.vector.app.features.login2.LoginAction2 import im.vector.app.features.login2.LoginCaptchaFragment2 import im.vector.app.features.login2.LoginFragmentSigninPassword2 @@ -66,6 +65,7 @@ import im.vector.app.features.login2.created.AccountCreatedFragment import im.vector.app.features.login2.terms.LoginTermsFragment2 import org.matrix.android.sdk.api.auth.registration.FlowResult import org.matrix.android.sdk.api.auth.registration.Stage +import org.matrix.android.sdk.api.auth.toLocalizedLoginTerms import org.matrix.android.sdk.api.extensions.tryOrNull private const val FRAGMENT_REGISTRATION_STAGE_TAG = "FRAGMENT_REGISTRATION_STAGE_TAG" @@ -169,9 +169,11 @@ class Login2Variant( // ft.setCustomAnimations(enterAnim, exitAnim, popEnterAnim, popExitAnim) }) is LoginViewEvents2.OpenHomeServerUrlFormScreen -> { - activity.addFragmentToBackstack(views.loginFragmentContainer, + activity.addFragmentToBackstack( + views.loginFragmentContainer, LoginServerUrlFormFragment2::class.java, - option = commonOption) + option = commonOption + ) } is LoginViewEvents2.OpenSignInEnterIdentifierScreen -> { activity.addFragmentToBackstack(views.loginFragmentContainer, @@ -187,67 +189,87 @@ class Login2Variant( }) } is LoginViewEvents2.OpenSsoOnlyScreen -> { - activity.addFragmentToBackstack(views.loginFragmentContainer, + activity.addFragmentToBackstack( + views.loginFragmentContainer, LoginSsoOnlyFragment2::class.java, - option = commonOption) + option = commonOption + ) } is LoginViewEvents2.OnWebLoginError -> onWebLoginError(event) is LoginViewEvents2.OpenResetPasswordScreen -> - activity.addFragmentToBackstack(views.loginFragmentContainer, + activity.addFragmentToBackstack( + views.loginFragmentContainer, LoginResetPasswordFragment2::class.java, - option = commonOption) + option = commonOption + ) is LoginViewEvents2.OnResetPasswordSendThreePidDone -> { supportFragmentManager.popBackStack(FRAGMENT_LOGIN_TAG, POP_BACK_STACK_EXCLUSIVE) - activity.addFragmentToBackstack(views.loginFragmentContainer, + activity.addFragmentToBackstack( + views.loginFragmentContainer, LoginResetPasswordMailConfirmationFragment2::class.java, - option = commonOption) + option = commonOption + ) } is LoginViewEvents2.OnResetPasswordMailConfirmationSuccess -> { supportFragmentManager.popBackStack(FRAGMENT_LOGIN_TAG, POP_BACK_STACK_EXCLUSIVE) - activity.addFragmentToBackstack(views.loginFragmentContainer, + activity.addFragmentToBackstack( + views.loginFragmentContainer, LoginResetPasswordSuccessFragment2::class.java, - option = commonOption) + option = commonOption + ) } is LoginViewEvents2.OnResetPasswordMailConfirmationSuccessDone -> { // Go back to the login fragment supportFragmentManager.popBackStack(FRAGMENT_LOGIN_TAG, POP_BACK_STACK_EXCLUSIVE) } is LoginViewEvents2.OnSendEmailSuccess -> - activity.addFragmentToBackstack(views.loginFragmentContainer, + activity.addFragmentToBackstack( + views.loginFragmentContainer, LoginWaitForEmailFragment2::class.java, LoginWaitForEmailFragmentArgument(event.email), tag = FRAGMENT_REGISTRATION_STAGE_TAG, - option = commonOption) + option = commonOption + ) is LoginViewEvents2.OpenSigninPasswordScreen -> { - activity.addFragmentToBackstack(views.loginFragmentContainer, + activity.addFragmentToBackstack( + views.loginFragmentContainer, LoginFragmentSigninPassword2::class.java, tag = FRAGMENT_LOGIN_TAG, - option = commonOption) + option = commonOption + ) } is LoginViewEvents2.OpenSignupPasswordScreen -> { - activity.addFragmentToBackstack(views.loginFragmentContainer, + activity.addFragmentToBackstack( + views.loginFragmentContainer, LoginFragmentSignupPassword2::class.java, tag = FRAGMENT_REGISTRATION_STAGE_TAG, - option = commonOption) + option = commonOption + ) } is LoginViewEvents2.OpenSignUpChooseUsernameScreen -> { - activity.addFragmentToBackstack(views.loginFragmentContainer, + activity.addFragmentToBackstack( + views.loginFragmentContainer, LoginFragmentSignupUsername2::class.java, tag = FRAGMENT_REGISTRATION_STAGE_TAG, - option = commonOption) + option = commonOption + ) } is LoginViewEvents2.OpenSignInWithAnythingScreen -> { - activity.addFragmentToBackstack(views.loginFragmentContainer, + activity.addFragmentToBackstack( + views.loginFragmentContainer, LoginFragmentToAny2::class.java, tag = FRAGMENT_LOGIN_TAG, - option = commonOption) + option = commonOption + ) } is LoginViewEvents2.OnSendMsisdnSuccess -> - activity.addFragmentToBackstack(views.loginFragmentContainer, + activity.addFragmentToBackstack( + views.loginFragmentContainer, LoginGenericTextInputFormFragment2::class.java, LoginGenericTextInputFormFragmentArgument(TextInputFormFragmentMode.ConfirmMsisdn, true, event.msisdn), tag = FRAGMENT_REGISTRATION_STAGE_TAG, - option = commonOption) + option = commonOption + ) is LoginViewEvents2.Failure -> // This is handled by the Fragments Unit @@ -268,9 +290,11 @@ class Login2Variant( if (event.newAccount) { // Propose to set avatar and display name // Back on this Fragment will finish the Activity - activity.addFragmentToBackstack(views.loginFragmentContainer, + activity.addFragmentToBackstack( + views.loginFragmentContainer, AccountCreatedFragment::class.java, - option = commonOption) + option = commonOption + ) } else { terminate(false) } @@ -285,9 +309,9 @@ class Login2Variant( activity.finish() } - private fun updateWithState(LoginViewState2: LoginViewState2) { + private fun updateWithState(loginViewState2: LoginViewState2) { // Loading - setIsLoading(LoginViewState2.isLoading) + setIsLoading(loginViewState2.isLoading) } // Hack for AccountCreatedFragment @@ -308,7 +332,7 @@ class Login2Variant( } /** - * Handle the SSO redirection here + * Handle the SSO redirection here. */ override fun onNewIntent(intent: Intent?) { intent?.data @@ -321,9 +345,11 @@ class Login2Variant( .setTitle(R.string.app_name) .setMessage(activity.getString(R.string.login_registration_not_supported)) .setPositiveButton(R.string.yes) { _, _ -> - activity.addFragmentToBackstack(views.loginFragmentContainer, + activity.addFragmentToBackstack( + views.loginFragmentContainer, LoginWebFragment2::class.java, - option = commonOption) + option = commonOption + ) } .setNegativeButton(R.string.no, null) .show() @@ -334,9 +360,11 @@ class Login2Variant( .setTitle(R.string.app_name) .setMessage(activity.getString(R.string.login_mode_not_supported, supportedTypes.joinToString { "'$it'" })) .setPositiveButton(R.string.yes) { _, _ -> - activity.addFragmentToBackstack(views.loginFragmentContainer, + activity.addFragmentToBackstack( + views.loginFragmentContainer, LoginWebFragment2::class.java, - option = commonOption) + option = commonOption + ) } .setNegativeButton(R.string.no, null) .show() @@ -364,26 +392,34 @@ class Login2Variant( supportFragmentManager.popBackStack(FRAGMENT_REGISTRATION_STAGE_TAG, FragmentManager.POP_BACK_STACK_INCLUSIVE) when (stage) { - is Stage.ReCaptcha -> activity.addFragmentToBackstack(views.loginFragmentContainer, + is Stage.ReCaptcha -> activity.addFragmentToBackstack( + views.loginFragmentContainer, LoginCaptchaFragment2::class.java, LoginCaptchaFragmentArgument(stage.publicKey), tag = FRAGMENT_REGISTRATION_STAGE_TAG, - option = commonOption) - is Stage.Email -> activity.addFragmentToBackstack(views.loginFragmentContainer, + option = commonOption + ) + is Stage.Email -> activity.addFragmentToBackstack( + views.loginFragmentContainer, LoginGenericTextInputFormFragment2::class.java, LoginGenericTextInputFormFragmentArgument(TextInputFormFragmentMode.SetEmail, stage.mandatory), tag = FRAGMENT_REGISTRATION_STAGE_TAG, - option = commonOption) - is Stage.Msisdn -> activity.addFragmentToBackstack(views.loginFragmentContainer, + option = commonOption + ) + is Stage.Msisdn -> activity.addFragmentToBackstack( + views.loginFragmentContainer, LoginGenericTextInputFormFragment2::class.java, LoginGenericTextInputFormFragmentArgument(TextInputFormFragmentMode.SetMsisdn, stage.mandatory), tag = FRAGMENT_REGISTRATION_STAGE_TAG, - option = commonOption) - is Stage.Terms -> activity.addFragmentToBackstack(views.loginFragmentContainer, + option = commonOption + ) + is Stage.Terms -> activity.addFragmentToBackstack( + views.loginFragmentContainer, LoginTermsFragment2::class.java, LoginTermsFragmentArgument(stage.policies.toLocalizedLoginTerms(activity.getString(R.string.resources_language))), tag = FRAGMENT_REGISTRATION_STAGE_TAG, - option = commonOption) + option = commonOption + ) else -> Unit // Should not happen } } diff --git a/vector/src/main/java/im/vector/app/features/onboarding/OnboardingViewEvents.kt b/vector/src/main/java/im/vector/app/features/onboarding/OnboardingViewEvents.kt index ee406aff48..6ffece4ab6 100644 --- a/vector/src/main/java/im/vector/app/features/onboarding/OnboardingViewEvents.kt +++ b/vector/src/main/java/im/vector/app/features/onboarding/OnboardingViewEvents.kt @@ -23,7 +23,7 @@ import im.vector.app.features.login.SignMode import org.matrix.android.sdk.api.auth.registration.FlowResult /** - * Transient events for Login + * Transient events for Login. */ sealed class OnboardingViewEvents : VectorViewEvents { data class Loading(val message: CharSequence? = null) : OnboardingViewEvents() 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 2b286e6d93..03526b47a5 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 @@ -562,7 +562,7 @@ class OnboardingViewModel @AssistedInject constructor( private suspend fun createPersonalizationState(session: Session, state: OnboardingViewState): PersonalizationState { return when { vectorFeatures.isOnboardingPersonalizeEnabled() -> { - val homeServerCapabilities = session.getHomeServerCapabilities() + val homeServerCapabilities = session.homeServerCapabilitiesService().getHomeServerCapabilities() val capabilityOverrides = vectorOverrides.forceHomeserverCapabilities?.firstOrNull() state.personalizationState.copy( supportsChangingDisplayName = capabilityOverrides?.canChangeDisplayName ?: homeServerCapabilities.canChangeDisplayName, @@ -645,7 +645,7 @@ class OnboardingViewModel @AssistedInject constructor( when (awaitState().onboardingFlow) { OnboardingFlow.SignIn -> { updateSignMode(SignMode.SignIn) - internalRegisterAction(RegisterAction.StartRegistration, ::emitFlowResultViewEvent) + _viewEvents.post(OnboardingViewEvents.OnSignModeSelected(SignMode.SignIn)) } OnboardingFlow.SignUp -> { updateSignMode(SignMode.SignUp) @@ -677,8 +677,8 @@ class OnboardingViewModel @AssistedInject constructor( } /** - * If user has entered https://matrix.org, ensure that server type is ServerType.MatrixOrg - * It is also useful to set the value again in the case of a certificate error on matrix.org + * If user has entered https://matrix.org, ensure that server type is ServerType.MatrixOrg. + * It is also useful to set the value again in the case of a certificate error on matrix.org. **/ private fun OnboardingViewState.alignServerTypeAfterSubmission(config: HomeServerConnectionConfig, serverTypeOverride: ServerType?): ServerType { return if (config.homeServerUri.toString() == matrixOrgUrl) { @@ -705,7 +705,7 @@ class OnboardingViewModel @AssistedInject constructor( viewModelScope.launch { val activeSession = activeSessionHolder.getActiveSession() try { - activeSession.setDisplayName(activeSession.myUserId, displayName) + activeSession.profileService().setDisplayName(activeSession.myUserId, displayName) setState { copy( isLoading = false, @@ -760,7 +760,7 @@ class OnboardingViewModel @AssistedInject constructor( viewModelScope.launch { val activeSession = activeSessionHolder.getActiveSession() try { - activeSession.updateAvatar( + activeSession.profileService().updateAvatar( activeSession.myUserId, pictureUri, uriFilenameResolver.getFilenameFromUri(pictureUri) ?: UUID.randomUUID().toString() diff --git a/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/AbstractFtueAuthFragment.kt b/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/AbstractFtueAuthFragment.kt index 64e29766c5..f5c81773ea 100644 --- a/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/AbstractFtueAuthFragment.kt +++ b/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/AbstractFtueAuthFragment.kt @@ -36,7 +36,7 @@ import kotlinx.coroutines.CancellationException import org.matrix.android.sdk.api.failure.Failure /** - * Parent Fragment for all the login/registration screens + * Parent Fragment for all the login/registration screens. */ abstract class AbstractFtueAuthFragment : VectorBaseFragment(), OnBackPressed { diff --git a/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/CaptchaWebview.kt b/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/CaptchaWebview.kt index 558c26fe56..c4099effa7 100644 --- a/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/CaptchaWebview.kt +++ b/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/CaptchaWebview.kt @@ -103,12 +103,14 @@ class CaptchaWebview @Inject constructor( } } + @Deprecated("Deprecated in Java") override fun onReceivedError(view: WebView, errorCode: Int, description: String, failingUrl: String) { @Suppress("DEPRECATION") super.onReceivedError(view, errorCode, description, failingUrl) onError(description) } + @Deprecated("Deprecated in Java") override fun shouldOverrideUrlLoading(view: WebView?, url: String?): Boolean { if (url?.startsWith("js:") == true) { val javascriptResponse = parseJsonFromUrl(url) diff --git a/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/FtueAuthAccountCreatedFragment.kt b/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/FtueAuthAccountCreatedFragment.kt index 49db52da67..b8114b5d94 100644 --- a/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/FtueAuthAccountCreatedFragment.kt +++ b/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/FtueAuthAccountCreatedFragment.kt @@ -24,6 +24,7 @@ import androidx.core.view.isVisible import im.vector.app.R import im.vector.app.core.animations.play import im.vector.app.core.di.ActiveSessionHolder +import im.vector.app.core.utils.isAnimationEnabled import im.vector.app.databinding.FragmentFtueAccountCreatedBinding import im.vector.app.features.onboarding.OnboardingAction import im.vector.app.features.onboarding.OnboardingViewEvents @@ -57,7 +58,7 @@ class FtueAuthAccountCreatedFragment @Inject constructor( views.personalizeButtonGroup.isVisible = canPersonalize views.takeMeHomeButtonGroup.isVisible = !canPersonalize - if (!hasPlayedConfetti && !canPersonalize) { + if (!hasPlayedConfetti && !canPersonalize && requireContext().isAnimationEnabled()) { hasPlayedConfetti = true views.viewKonfetti.isVisible = true views.viewKonfetti.play() diff --git a/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/FtueAuthCaptchaFragment.kt b/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/FtueAuthCaptchaFragment.kt index a3665a8f40..3720a41455 100644 --- a/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/FtueAuthCaptchaFragment.kt +++ b/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/FtueAuthCaptchaFragment.kt @@ -33,7 +33,7 @@ data class FtueAuthCaptchaFragmentArgument( ) : Parcelable /** - * In this screen, the user is asked to confirm they are not a robot + * In this screen, the user is asked to confirm they are not a robot. */ class FtueAuthCaptchaFragment @Inject constructor( private val captchaWebview: CaptchaWebview diff --git a/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/FtueAuthChooseProfilePictureFragment.kt b/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/FtueAuthChooseProfilePictureFragment.kt index 81300932db..0ccff7dffc 100644 --- a/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/FtueAuthChooseProfilePictureFragment.kt +++ b/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/FtueAuthChooseProfilePictureFragment.kt @@ -29,6 +29,7 @@ import im.vector.app.core.di.ActiveSessionHolder import im.vector.app.core.dialogs.GalleryOrCameraDialogHelper import im.vector.app.core.extensions.singletonEntryPoint import im.vector.app.core.resources.ColorProvider +import im.vector.app.core.time.Clock import im.vector.app.databinding.FragmentFtueProfilePictureBinding import im.vector.app.features.home.AvatarRenderer import im.vector.app.features.onboarding.OnboardingAction @@ -39,10 +40,11 @@ import javax.inject.Inject class FtueAuthChooseProfilePictureFragment @Inject constructor( private val activeSessionHolder: ActiveSessionHolder, - colorProvider: ColorProvider + colorProvider: ColorProvider, + clock: Clock, ) : AbstractFtueAuthFragment(), GalleryOrCameraDialogHelper.Listener { - private val galleryOrCameraDialogHelper = GalleryOrCameraDialogHelper(this, colorProvider) + private val galleryOrCameraDialogHelper = GalleryOrCameraDialogHelper(this, colorProvider, clock) private val avatarRenderer: AvatarRenderer by lazy { requireContext().singletonEntryPoint().avatarRenderer() } override fun getBinding(inflater: LayoutInflater, container: ViewGroup?): FragmentFtueProfilePictureBinding { diff --git a/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/FtueAuthGenericTextInputFormFragment.kt b/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/FtueAuthGenericTextInputFormFragment.kt index 466e141fdf..ce3dee7a19 100644 --- a/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/FtueAuthGenericTextInputFormFragment.kt +++ b/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/FtueAuthGenericTextInputFormFragment.kt @@ -55,7 +55,7 @@ data class FtueAuthGenericTextInputFormFragmentArgument( ) : Parcelable /** - * In this screen, the user is asked for a text input + * In this screen, the user is asked for a text input. */ class FtueAuthGenericTextInputFormFragment @Inject constructor() : AbstractFtueAuthFragment() { diff --git a/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/FtueAuthLegacyStyleCaptchaFragment.kt b/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/FtueAuthLegacyStyleCaptchaFragment.kt index 2accab00e0..b8b30529a6 100644 --- a/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/FtueAuthLegacyStyleCaptchaFragment.kt +++ b/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/FtueAuthLegacyStyleCaptchaFragment.kt @@ -33,7 +33,7 @@ data class FtueAuthLegacyStyleCaptchaFragmentArgument( ) : Parcelable /** - * In this screen, the user is asked to confirm they are not a robot + * In this screen, the user is asked to confirm they are not a robot. */ class FtueAuthLegacyStyleCaptchaFragment @Inject constructor( private val captchaWebview: CaptchaWebview diff --git a/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/FtueAuthLoginFragment.kt b/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/FtueAuthLoginFragment.kt index 4888b43946..2308280400 100644 --- a/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/FtueAuthLoginFragment.kt +++ b/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/FtueAuthLoginFragment.kt @@ -127,11 +127,13 @@ class FtueAuthLoginFragment @Inject constructor() : AbstractSSOFtueAuthFragment< // This can be called by the IME action, so deal with empty cases var error = 0 if (login.isEmpty()) { - views.loginFieldTil.error = getString(if (isSignupMode) { - R.string.error_empty_field_choose_user_name - } else { - R.string.error_empty_field_enter_user_name - }) + views.loginFieldTil.error = getString( + if (isSignupMode) { + R.string.error_empty_field_choose_user_name + } else { + R.string.error_empty_field_enter_user_name + } + ) error++ } if (isSignupMode && isNumericOnlyUserIdForbidden && login.isDigitsOnly()) { @@ -139,11 +141,13 @@ class FtueAuthLoginFragment @Inject constructor() : AbstractSSOFtueAuthFragment< error++ } if (password.isEmpty()) { - views.passwordFieldTil.error = getString(if (isSignupMode) { - R.string.error_empty_field_choose_password - } else { - R.string.error_empty_field_your_password - }) + views.passwordFieldTil.error = getString( + if (isSignupMode) { + R.string.error_empty_field_choose_password + } else { + R.string.error_empty_field_your_password + } + ) error++ } @@ -159,12 +163,14 @@ class FtueAuthLoginFragment @Inject constructor() : AbstractSSOFtueAuthFragment< } private fun setupUi(state: OnboardingViewState) { - views.loginFieldTil.hint = getString(when (state.signMode) { - SignMode.Unknown -> error("developer error") - SignMode.SignUp -> R.string.login_signup_username_hint - SignMode.SignIn -> R.string.login_signin_username_hint - SignMode.SignInWithMatrixId -> R.string.login_signin_matrix_id_hint - }) + views.loginFieldTil.hint = getString( + when (state.signMode) { + SignMode.Unknown -> error("developer error") + SignMode.SignUp -> R.string.login_signup_username_hint + SignMode.SignIn -> R.string.login_signin_username_hint + SignMode.SignInWithMatrixId -> R.string.login_signin_matrix_id_hint + } + ) // Handle direct signin first if (state.signMode == SignMode.SignInWithMatrixId) { @@ -225,12 +231,14 @@ class FtueAuthLoginFragment @Inject constructor() : AbstractSSOFtueAuthFragment< private fun setupButtons(state: OnboardingViewState) { views.forgetPasswordButton.isVisible = state.signMode == SignMode.SignIn - views.loginSubmit.text = getString(when (state.signMode) { - SignMode.Unknown -> error("developer error") - SignMode.SignUp -> R.string.login_signup_submit - SignMode.SignIn, - SignMode.SignInWithMatrixId -> R.string.login_signin - }) + views.loginSubmit.text = getString( + when (state.signMode) { + SignMode.Unknown -> error("developer error") + SignMode.SignUp -> R.string.login_signup_submit + SignMode.SignIn, + SignMode.SignInWithMatrixId -> R.string.login_signin + } + ) } private fun setupSubmitButton() { @@ -302,7 +310,7 @@ class FtueAuthLoginFragment @Inject constructor() : AbstractSSOFtueAuthFragment< } /** - * Detect if password ends or starts with spaces + * Detect if password ends or starts with spaces. */ private fun spaceInPassword() = views.passwordField.text.toString().let { it.trim() != it } } diff --git a/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/FtueAuthPersonalizationCompleteFragment.kt b/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/FtueAuthPersonalizationCompleteFragment.kt index 6b47b9830c..074f58864e 100644 --- a/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/FtueAuthPersonalizationCompleteFragment.kt +++ b/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/FtueAuthPersonalizationCompleteFragment.kt @@ -22,6 +22,7 @@ import android.view.View import android.view.ViewGroup import androidx.core.view.isVisible import im.vector.app.core.animations.play +import im.vector.app.core.utils.isAnimationEnabled import im.vector.app.databinding.FragmentFtuePersonalizationCompleteBinding import im.vector.app.features.onboarding.OnboardingAction import im.vector.app.features.onboarding.OnboardingViewEvents @@ -43,7 +44,7 @@ class FtueAuthPersonalizationCompleteFragment @Inject constructor() : AbstractFt private fun setupViews() { views.personalizationCompleteCta.debouncedClicks { viewModel.handle(OnboardingAction.PostViewEvent(OnboardingViewEvents.OnTakeMeHome)) } - if (!hasPlayedConfetti) { + if (!hasPlayedConfetti && requireContext().isAnimationEnabled()) { hasPlayedConfetti = true views.viewKonfetti.isVisible = true views.viewKonfetti.play() diff --git a/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/FtueAuthResetPasswordFragment.kt b/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/FtueAuthResetPasswordFragment.kt index 4ec02f677a..9bef084750 100644 --- a/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/FtueAuthResetPasswordFragment.kt +++ b/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/FtueAuthResetPasswordFragment.kt @@ -38,7 +38,7 @@ import reactivecircus.flowbinding.android.widget.textChanges import javax.inject.Inject /** - * In this screen, the user is asked for email and new password to reset his password + * In this screen, the user is asked for email and new password to reset his password. */ class FtueAuthResetPasswordFragment @Inject constructor() : AbstractFtueAuthFragment() { diff --git a/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/FtueAuthResetPasswordMailConfirmationFragment.kt b/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/FtueAuthResetPasswordMailConfirmationFragment.kt index f6141a4900..fd7f14b1cc 100644 --- a/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/FtueAuthResetPasswordMailConfirmationFragment.kt +++ b/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/FtueAuthResetPasswordMailConfirmationFragment.kt @@ -29,7 +29,7 @@ import org.matrix.android.sdk.api.failure.is401 import javax.inject.Inject /** - * In this screen, the user is asked to check his email and to click on a button once it's done + * In this screen, the user is asked to check their email and to click on a button once it's done. */ class FtueAuthResetPasswordMailConfirmationFragment @Inject constructor() : AbstractFtueAuthFragment() { diff --git a/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/FtueAuthResetPasswordSuccessFragment.kt b/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/FtueAuthResetPasswordSuccessFragment.kt index 74b1a7f8c2..adc6e1b214 100644 --- a/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/FtueAuthResetPasswordSuccessFragment.kt +++ b/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/FtueAuthResetPasswordSuccessFragment.kt @@ -26,7 +26,7 @@ import im.vector.app.features.onboarding.OnboardingViewEvents import javax.inject.Inject /** - * In this screen, we confirm to the user that his password has been reset + * In this screen, we confirm to the user that his password has been reset. */ class FtueAuthResetPasswordSuccessFragment @Inject constructor() : AbstractFtueAuthFragment() { diff --git a/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/FtueAuthServerSelectionFragment.kt b/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/FtueAuthServerSelectionFragment.kt index f72bd2c5d3..d4396d81d2 100644 --- a/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/FtueAuthServerSelectionFragment.kt +++ b/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/FtueAuthServerSelectionFragment.kt @@ -32,7 +32,7 @@ import me.gujun.android.span.span import javax.inject.Inject /** - * In this screen, the user will choose between matrix.org, modular or other type of homeserver + * In this screen, the user will choose between matrix.org, modular or other type of homeserver. */ class FtueAuthServerSelectionFragment @Inject constructor() : AbstractFtueAuthFragment() { diff --git a/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/FtueAuthServerUrlFormFragment.kt b/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/FtueAuthServerUrlFormFragment.kt index df304d028d..7c2fb1b8e2 100644 --- a/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/FtueAuthServerUrlFormFragment.kt +++ b/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/FtueAuthServerUrlFormFragment.kt @@ -45,7 +45,7 @@ import java.net.UnknownHostException import javax.inject.Inject /** - * In this screen, the user is prompted to enter a homeserver url + * In this screen, the user is prompted to enter a homeserver url. */ class FtueAuthServerUrlFormFragment @Inject constructor() : AbstractFtueAuthFragment() { @@ -104,11 +104,13 @@ class FtueAuthServerUrlFormFragment @Inject constructor() : AbstractFtueAuthFrag } } val completions = state.knownCustomHomeServersUrls + if (BuildConfig.DEBUG) listOf("http://10.0.2.2:8080") else emptyList() - views.loginServerUrlFormHomeServerUrl.setAdapter(ArrayAdapter( - requireContext(), - R.layout.item_completion_homeserver, - completions - )) + views.loginServerUrlFormHomeServerUrl.setAdapter( + ArrayAdapter( + requireContext(), + R.layout.item_completion_homeserver, + completions + ) + ) views.loginServerUrlFormHomeServerUrlTil.endIconMode = TextInputLayout.END_ICON_DROPDOWN_MENU .takeIf { completions.isNotEmpty() } ?: TextInputLayout.END_ICON_NONE diff --git a/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/FtueAuthSignUpSignInSelectionFragment.kt b/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/FtueAuthSignUpSignInSelectionFragment.kt index dda5e0c36a..69fbd3459b 100644 --- a/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/FtueAuthSignUpSignInSelectionFragment.kt +++ b/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/FtueAuthSignUpSignInSelectionFragment.kt @@ -37,7 +37,7 @@ import im.vector.app.features.onboarding.OnboardingViewState import javax.inject.Inject /** - * In this screen, the user is asked to sign up or to sign in to the homeserver + * In this screen, the user is asked to sign up or to sign in to the homeserver. */ class FtueAuthSignUpSignInSelectionFragment @Inject constructor() : AbstractSSOFtueAuthFragment() { diff --git a/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/FtueAuthSplashFragment.kt b/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/FtueAuthSplashFragment.kt index 031579db5f..2fa3b52706 100644 --- a/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/FtueAuthSplashFragment.kt +++ b/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/FtueAuthSplashFragment.kt @@ -36,7 +36,7 @@ import java.net.UnknownHostException import javax.inject.Inject /** - * In this screen, the user is viewing an introduction to what he can do with this application + * In this screen, the user is viewing an introduction to what he can do with this application. */ class FtueAuthSplashFragment @Inject constructor( private val vectorPreferences: VectorPreferences, diff --git a/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/FtueAuthVariant.kt b/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/FtueAuthVariant.kt index d5816762df..6f1b85df4f 100644 --- a/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/FtueAuthVariant.kt +++ b/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/FtueAuthVariant.kt @@ -45,7 +45,6 @@ import im.vector.app.features.login.ServerType import im.vector.app.features.login.SignMode import im.vector.app.features.login.TextInputFormFragmentMode import im.vector.app.features.login.isSupported -import im.vector.app.features.login.terms.toLocalizedLoginTerms import im.vector.app.features.onboarding.OnboardingAction import im.vector.app.features.onboarding.OnboardingActivity import im.vector.app.features.onboarding.OnboardingVariant @@ -57,6 +56,7 @@ import im.vector.app.features.onboarding.ftueauth.terms.FtueAuthTermsFragment import im.vector.app.features.onboarding.ftueauth.terms.FtueAuthTermsLegacyStyleFragmentArgument import org.matrix.android.sdk.api.auth.registration.FlowResult import org.matrix.android.sdk.api.auth.registration.Stage +import org.matrix.android.sdk.api.auth.toLocalizedLoginTerms import org.matrix.android.sdk.api.extensions.tryOrNull private const val FRAGMENT_REGISTRATION_STAGE_TAG = "FRAGMENT_REGISTRATION_STAGE_TAG" @@ -131,24 +131,7 @@ class FtueAuthVariant( private fun handleOnboardingViewEvents(viewEvents: OnboardingViewEvents) { when (viewEvents) { is OnboardingViewEvents.RegistrationFlowResult -> { - if (registrationShouldFallback(viewEvents)) { - // Display a popup to propose use web fallback - onRegistrationStageNotSupported() - } else { - if (viewEvents.isRegistrationStarted) { - // Go on with registration flow - handleRegistrationNavigation(viewEvents.flowResult) - } else { - if (vectorFeatures.isOnboardingCombinedRegisterEnabled()) { - openCombinedRegister() - } else { - // First ask for login and password - // I add a tag to indicate that this fragment is a registration stage. - // This way it will be automatically popped in when starting the next registration stage - openAuthLoginFragmentWithTag(FRAGMENT_REGISTRATION_STAGE_TAG) - } - } - } + onRegistrationFlow(viewEvents) } is OnboardingViewEvents.OutdatedHomeserver -> { MaterialAlertDialogBuilder(activity) @@ -176,25 +159,33 @@ class FtueAuthVariant( is OnboardingViewEvents.OnServerSelectionDone -> onServerSelectionDone(viewEvents) is OnboardingViewEvents.OnSignModeSelected -> onSignModeSelected(viewEvents) is OnboardingViewEvents.OnLoginFlowRetrieved -> - activity.addFragmentToBackstack(views.loginFragmentContainer, + activity.addFragmentToBackstack( + views.loginFragmentContainer, FtueAuthSignUpSignInSelectionFragment::class.java, - option = commonOption) + option = commonOption + ) is OnboardingViewEvents.OnWebLoginError -> onWebLoginError(viewEvents) is OnboardingViewEvents.OnForgetPasswordClicked -> - activity.addFragmentToBackstack(views.loginFragmentContainer, + activity.addFragmentToBackstack( + views.loginFragmentContainer, FtueAuthResetPasswordFragment::class.java, - option = commonOption) + option = commonOption + ) is OnboardingViewEvents.OnResetPasswordSendThreePidDone -> { supportFragmentManager.popBackStack(FRAGMENT_LOGIN_TAG, POP_BACK_STACK_EXCLUSIVE) - activity.addFragmentToBackstack(views.loginFragmentContainer, + activity.addFragmentToBackstack( + views.loginFragmentContainer, FtueAuthResetPasswordMailConfirmationFragment::class.java, - option = commonOption) + option = commonOption + ) } is OnboardingViewEvents.OnResetPasswordMailConfirmationSuccess -> { supportFragmentManager.popBackStack(FRAGMENT_LOGIN_TAG, POP_BACK_STACK_EXCLUSIVE) - activity.addFragmentToBackstack(views.loginFragmentContainer, + activity.addFragmentToBackstack( + views.loginFragmentContainer, FtueAuthResetPasswordSuccessFragment::class.java, - option = commonOption) + option = commonOption + ) } is OnboardingViewEvents.OnResetPasswordMailConfirmationSuccessDone -> { // Go back to the login fragment @@ -221,11 +212,13 @@ class FtueAuthVariant( // This is handled by the Fragments Unit OnboardingViewEvents.OpenUseCaseSelection -> { - activity.addFragmentToBackstack(views.loginFragmentContainer, + activity.addFragmentToBackstack( + views.loginFragmentContainer, FtueAuthUseCaseFragment::class.java, - option = commonOption) + option = commonOption + ) } - OnboardingViewEvents.OpenCombinedRegister -> openCombinedRegister() + OnboardingViewEvents.OpenCombinedRegister -> openStartCombinedRegister() is OnboardingViewEvents.OnAccountCreated -> onAccountCreated() OnboardingViewEvents.OnAccountSignedIn -> onAccountSignedIn() OnboardingViewEvents.OnChooseDisplayName -> onChooseDisplayName() @@ -244,7 +237,21 @@ class FtueAuthVariant( } } - private fun openCombinedRegister() { + private fun onRegistrationFlow(viewEvents: OnboardingViewEvents.RegistrationFlowResult) { + when { + registrationShouldFallback(viewEvents) -> displayFallbackWebDialog() + viewEvents.isRegistrationStarted -> handleRegistrationNavigation(viewEvents.flowResult.orderedStages()) + vectorFeatures.isOnboardingCombinedRegisterEnabled() -> openStartCombinedRegister() + else -> openAuthLoginFragmentWithTag(FRAGMENT_REGISTRATION_STAGE_TAG) + } + } + + private fun FlowResult.orderedStages() = when { + vectorFeatures.isOnboardingCombinedRegisterEnabled() -> missingStages.sortedWith(FtueMissingRegistrationStagesComparator()) + else -> missingStages + } + + private fun openStartCombinedRegister() { addRegistrationStageFragmentToBackstack(FtueAuthCombinedRegisterFragment::class.java) } @@ -254,14 +261,16 @@ class FtueAuthVariant( private fun OnboardingViewEvents.RegistrationFlowResult.containsUnsupportedRegistrationFlow() = flowResult.missingStages.any { !it.isSupported() } - private fun onRegistrationStageNotSupported() { + private fun displayFallbackWebDialog() { MaterialAlertDialogBuilder(activity) .setTitle(R.string.app_name) .setMessage(activity.getString(R.string.login_registration_not_supported)) .setPositiveButton(R.string.yes) { _, _ -> - activity.addFragmentToBackstack(views.loginFragmentContainer, + activity.addFragmentToBackstack( + views.loginFragmentContainer, FtueAuthWebFragment::class.java, - option = commonOption) + option = commonOption + ) } .setNegativeButton(R.string.no, null) .show() @@ -279,20 +288,22 @@ class FtueAuthVariant( .show() } - private fun onServerSelectionDone(OnboardingViewEvents: OnboardingViewEvents.OnServerSelectionDone) { - when (OnboardingViewEvents.serverType) { + private fun onServerSelectionDone(onboardingViewEvents: OnboardingViewEvents.OnServerSelectionDone) { + when (onboardingViewEvents.serverType) { ServerType.MatrixOrg -> Unit // In this case, we wait for the login flow ServerType.EMS, - ServerType.Other -> activity.addFragmentToBackstack(views.loginFragmentContainer, + ServerType.Other -> activity.addFragmentToBackstack( + views.loginFragmentContainer, FtueAuthServerUrlFormFragment::class.java, - option = commonOption) + option = commonOption + ) ServerType.Unknown -> Unit /* Should not happen */ } } - private fun onSignModeSelected(OnboardingViewEvents: OnboardingViewEvents.OnSignModeSelected) = withState(onboardingViewModel) { state -> + private fun onSignModeSelected(onboardingViewEvents: OnboardingViewEvents.OnSignModeSelected) = withState(onboardingViewModel) { state -> // state.signMode could not be ready yet. So use value from the ViewEvent - when (OnboardingViewEvents.signMode) { + when (onboardingViewEvents.signMode) { SignMode.Unknown -> error("Sign mode has to be set before calling this method") SignMode.SignUp -> Unit // This case is processed in handleOnboardingViewEvents SignMode.SignIn -> handleSignInSelected(state) @@ -317,10 +328,12 @@ class FtueAuthVariant( } private fun openAuthLoginFragmentWithTag(tag: String) { - activity.addFragmentToBackstack(views.loginFragmentContainer, + activity.addFragmentToBackstack( + views.loginFragmentContainer, FtueAuthLoginFragment::class.java, tag = tag, - option = commonOption) + option = commonOption + ) } private fun onLoginModeNotSupported(supportedTypes: List) { @@ -341,13 +354,15 @@ class FtueAuthVariant( } private fun openAuthWebFragment() { - activity.addFragmentToBackstack(views.loginFragmentContainer, + activity.addFragmentToBackstack( + views.loginFragmentContainer, FtueAuthWebFragment::class.java, - option = commonOption) + option = commonOption + ) } /** - * Handle the SSO redirection here + * Handle the SSO redirection here. */ override fun onNewIntent(intent: Intent?) { intent?.data @@ -355,15 +370,15 @@ class FtueAuthVariant( ?.let { onboardingViewModel.handle(OnboardingAction.LoginWithToken(it)) } } - private fun handleRegistrationNavigation(flowResult: FlowResult) { + private fun handleRegistrationNavigation(remainingStages: List) { // Complete all mandatory stages first - val mandatoryStage = flowResult.missingStages.firstOrNull { it.mandatory } + val mandatoryStage = remainingStages.firstOrNull { it.mandatory } if (mandatoryStage != null) { doStage(mandatoryStage) } else { // Consider optional stages - val optionalStage = flowResult.missingStages.firstOrNull { !it.mandatory && it !is Stage.Dummy } + val optionalStage = remainingStages.firstOrNull { !it.mandatory && it !is Stage.Dummy } if (optionalStage == null) { // Should not happen... } else { @@ -437,14 +452,16 @@ class FtueAuthVariant( } private fun onChooseDisplayName() { - activity.addFragmentToBackstack(views.loginFragmentContainer, + activity.addFragmentToBackstack( + views.loginFragmentContainer, FtueAuthChooseDisplayNameFragment::class.java, option = commonOption ) } private fun onChooseProfilePicture() { - activity.addFragmentToBackstack(views.loginFragmentContainer, + activity.addFragmentToBackstack( + views.loginFragmentContainer, FtueAuthChooseProfilePictureFragment::class.java, option = commonOption ) diff --git a/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/FtueAuthWaitForEmailFragment.kt b/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/FtueAuthWaitForEmailFragment.kt index 6056cd30d3..d78e0fe74d 100644 --- a/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/FtueAuthWaitForEmailFragment.kt +++ b/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/FtueAuthWaitForEmailFragment.kt @@ -36,7 +36,7 @@ data class FtueAuthWaitForEmailFragmentArgument( ) : Parcelable /** - * In this screen, the user is asked to check his emails + * In this screen, the user is asked to check their emails. */ class FtueAuthWaitForEmailFragment @Inject constructor() : AbstractFtueAuthFragment() { diff --git a/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/FtueAuthWebFragment.kt b/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/FtueAuthWebFragment.kt index 26b9a38d83..68e8632608 100644 --- a/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/FtueAuthWebFragment.kt +++ b/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/FtueAuthWebFragment.kt @@ -47,7 +47,7 @@ import javax.inject.Inject /** * This screen is displayed when the application does not support login flow or registration flow - * of the homeserver, as a fallback to login or to create an account + * of the homeserver, as a fallback to login or to create an account. */ class FtueAuthWebFragment @Inject constructor( private val assetReader: AssetReader @@ -138,6 +138,7 @@ class FtueAuthWebFragment @Inject constructor( .show() } + @Deprecated("Deprecated in Java") override fun onReceivedError(view: WebView, errorCode: Int, description: String, failingUrl: String) { super.onReceivedError(view, errorCode, description, failingUrl) @@ -186,10 +187,12 @@ class FtueAuthWebFragment @Inject constructor( * } * } *
+ * . * @param view * @param url * @return */ + @Deprecated("Deprecated in Java") override fun shouldOverrideUrlLoading(view: WebView, url: String?): Boolean { if (url == null) return super.shouldOverrideUrlLoading(view, url as String?) diff --git a/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/FtueMissingRegistrationStagesComparator.kt b/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/FtueMissingRegistrationStagesComparator.kt new file mode 100644 index 0000000000..6a6326625e --- /dev/null +++ b/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/FtueMissingRegistrationStagesComparator.kt @@ -0,0 +1,35 @@ +/* + * 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.onboarding.ftueauth + +import org.matrix.android.sdk.api.auth.registration.Stage + +class FtueMissingRegistrationStagesComparator : Comparator { + + override fun compare(a: Stage?, b: Stage?): Int { + return (a?.toPriority() ?: 0) - (b?.toPriority() ?: 0) + } + + private fun Stage.toPriority() = when (this) { + is Stage.Email -> 0 + is Stage.Msisdn -> 1 + is Stage.Terms -> 2 + is Stage.ReCaptcha -> 3 + is Stage.Other -> 4 + is Stage.Dummy -> 5 + } +} diff --git a/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/SplashCarouselStateFactory.kt b/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/SplashCarouselStateFactory.kt index 006492f6dc..23f7014374 100644 --- a/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/SplashCarouselStateFactory.kt +++ b/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/SplashCarouselStateFactory.kt @@ -41,32 +41,34 @@ class SplashCarouselStateFactory @Inject constructor( val lightTheme = themeProvider.isLightTheme() fun background(@DrawableRes lightDrawable: Int) = if (lightTheme) lightDrawable else R.drawable.bg_carousel_page_dark fun hero(@DrawableRes lightDrawable: Int, @DrawableRes darkDrawable: Int) = if (lightTheme) lightDrawable else darkDrawable - return SplashCarouselState(listOf( - SplashCarouselState.Item( - R.string.ftue_auth_carousel_secure_title.colorTerminatingFullStop(R.attr.colorAccent), - R.string.ftue_auth_carousel_secure_body, - hero(R.drawable.ic_splash_conversations, R.drawable.ic_splash_conversations_dark), - background(R.drawable.bg_carousel_page_1) - ), - SplashCarouselState.Item( - R.string.ftue_auth_carousel_control_title.colorTerminatingFullStop(R.attr.colorAccent), - R.string.ftue_auth_carousel_control_body, - hero(R.drawable.ic_splash_control, R.drawable.ic_splash_control_dark), - background(R.drawable.bg_carousel_page_2) - ), - SplashCarouselState.Item( - R.string.ftue_auth_carousel_encrypted_title.colorTerminatingFullStop(R.attr.colorAccent), - R.string.ftue_auth_carousel_encrypted_body, - hero(R.drawable.ic_splash_secure, R.drawable.ic_splash_secure_dark), - background(R.drawable.bg_carousel_page_3) - ), - SplashCarouselState.Item( - collaborationTitle().colorTerminatingFullStop(R.attr.colorAccent), - R.string.ftue_auth_carousel_workplace_body, - hero(R.drawable.ic_splash_collaboration, R.drawable.ic_splash_collaboration_dark), - background(R.drawable.bg_carousel_page_4) + return SplashCarouselState( + listOf( + SplashCarouselState.Item( + R.string.ftue_auth_carousel_secure_title.colorTerminatingFullStop(R.attr.colorAccent), + R.string.ftue_auth_carousel_secure_body, + hero(R.drawable.ic_splash_conversations, R.drawable.ic_splash_conversations_dark), + background(R.drawable.bg_carousel_page_1) + ), + SplashCarouselState.Item( + R.string.ftue_auth_carousel_control_title.colorTerminatingFullStop(R.attr.colorAccent), + R.string.ftue_auth_carousel_control_body, + hero(R.drawable.ic_splash_control, R.drawable.ic_splash_control_dark), + background(R.drawable.bg_carousel_page_2) + ), + SplashCarouselState.Item( + R.string.ftue_auth_carousel_encrypted_title.colorTerminatingFullStop(R.attr.colorAccent), + R.string.ftue_auth_carousel_encrypted_body, + hero(R.drawable.ic_splash_secure, R.drawable.ic_splash_secure_dark), + background(R.drawable.bg_carousel_page_3) + ), + SplashCarouselState.Item( + collaborationTitle().colorTerminatingFullStop(R.attr.colorAccent), + R.string.ftue_auth_carousel_workplace_body, + hero(R.drawable.ic_splash_collaboration, R.drawable.ic_splash_collaboration_dark), + background(R.drawable.bg_carousel_page_4) + ) ) - )) + ) } private fun collaborationTitle(): Int { diff --git a/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/terms/FtueAuthLegacyStyleTermsFragment.kt b/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/terms/FtueAuthLegacyStyleTermsFragment.kt index 727e3d5cbd..af38062663 100755 --- a/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/terms/FtueAuthLegacyStyleTermsFragment.kt +++ b/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/terms/FtueAuthLegacyStyleTermsFragment.kt @@ -44,7 +44,7 @@ data class FtueAuthTermsLegacyStyleFragmentArgument( ) : Parcelable /** - * LoginTermsFragment displays the list of policies the user has to accept + * LoginTermsFragment displays the list of policies the user has to accept. */ class FtueAuthLegacyStyleTermsFragment @Inject constructor( private val policyController: PolicyController diff --git a/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/terms/FtueAuthTermsFragment.kt b/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/terms/FtueAuthTermsFragment.kt index 4a25e35537..f168536575 100644 --- a/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/terms/FtueAuthTermsFragment.kt +++ b/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/terms/FtueAuthTermsFragment.kt @@ -41,7 +41,7 @@ import javax.inject.Inject import kotlin.math.roundToInt /** - * LoginTermsFragment displays the list of policies the user has to accept + * LoginTermsFragment displays the list of policies the user has to accept. */ class FtueAuthTermsFragment @Inject constructor( private val policyController: PolicyController diff --git a/vector/src/main/java/im/vector/app/features/permalink/PermalinkHandler.kt b/vector/src/main/java/im/vector/app/features/permalink/PermalinkHandler.kt index f840e9ea6f..5d1ba4e20c 100644 --- a/vector/src/main/java/im/vector/app/features/permalink/PermalinkHandler.kt +++ b/vector/src/main/java/im/vector/app/features/permalink/PermalinkHandler.kt @@ -25,6 +25,7 @@ import im.vector.app.core.extensions.isIgnored import im.vector.app.core.resources.UserPreferencesProvider import im.vector.app.core.utils.toast import im.vector.app.features.home.room.threads.arguments.ThreadTimelineArgs +import im.vector.app.features.matrixto.OriginOfMatrixTo import im.vector.app.features.navigation.Navigator import im.vector.app.features.roomdirectory.roompreview.RoomPreviewData import kotlinx.coroutines.Dispatchers @@ -32,9 +33,12 @@ import kotlinx.coroutines.withContext import org.matrix.android.sdk.api.extensions.orFalse import org.matrix.android.sdk.api.extensions.tryOrNull import org.matrix.android.sdk.api.session.events.model.getRootThreadEventId +import org.matrix.android.sdk.api.session.getRoom +import org.matrix.android.sdk.api.session.getRoomSummary import org.matrix.android.sdk.api.session.permalinks.PermalinkData import org.matrix.android.sdk.api.session.permalinks.PermalinkParser import org.matrix.android.sdk.api.session.permalinks.PermalinkService +import org.matrix.android.sdk.api.session.room.getTimelineEvent import org.matrix.android.sdk.api.session.room.model.Membership import org.matrix.android.sdk.api.session.room.model.RoomSummary import org.matrix.android.sdk.api.session.room.model.RoomType @@ -145,7 +149,7 @@ class PermalinkHandler @Inject constructor(private val activeSessionHolder: Acti private suspend fun PermalinkData.RoomLink.getRoomId(): String? { val session = activeSessionHolder.getSafeActiveSession() return if (isRoomAlias && session != null) { - val roomIdByAlias = session.getRoomIdByAlias(roomIdOrAlias, true) + val roomIdByAlias = session.roomService().getRoomIdByAlias(roomIdOrAlias, true) roomIdByAlias.getOrNull()?.roomId } else { roomIdOrAlias @@ -161,7 +165,7 @@ class PermalinkHandler @Inject constructor(private val activeSessionHolder: Acti } /** - * Open room either joined, or not + * Open room either joined, or not. */ private fun openRoom( navigationInterceptor: NavigationInterceptor?, @@ -190,12 +194,12 @@ class PermalinkHandler @Inject constructor(private val activeSessionHolder: Acti navigationInterceptor.openJoinedRoomScreen(buildTask, roomId, eventId, rawLink, context, rootThreadEventId, roomSummary) } else { // maybe open space preview navigator.openSpacePreview(context, roomId)? if already joined? - navigator.openMatrixToBottomSheet(context, rawLink.toString()) + navigator.openMatrixToBottomSheet(context, rawLink.toString(), OriginOfMatrixTo.LINK) } } else -> { // XXX this could trigger another server load - navigator.openMatrixToBottomSheet(context, rawLink.toString()) + navigator.openMatrixToBottomSheet(context, rawLink.toString(), OriginOfMatrixTo.LINK) } } } @@ -215,7 +219,8 @@ class PermalinkHandler @Inject constructor(private val activeSessionHolder: Acti displayName = roomSummary.displayName, avatarUrl = roomSummary.avatarUrl, roomEncryptionTrustLevel = roomSummary.roomEncryptionTrustLevel, - rootThreadEventId = rootThreadEventId) + rootThreadEventId = rootThreadEventId + ) navigator.openThread(context, threadTimelineArgs, eventId) } else { navigator.openRoom(context, roomId, eventId, buildTask) @@ -233,14 +238,14 @@ class PermalinkHandler @Inject constructor(private val activeSessionHolder: Acti interface NavigationInterceptor { /** - * Return true if the navigation has been intercepted + * Return true if the navigation has been intercepted. */ fun navToRoom(roomId: String?, eventId: String?, deepLink: Uri? = null, rootThreadEventId: String? = null): Boolean { return false } /** - * Return true if the navigation has been intercepted + * Return true if the navigation has been intercepted. */ fun navToMemberProfile(userId: String, deepLink: Uri): Boolean { return false diff --git a/vector/src/main/java/im/vector/app/features/pin/PinCodeStore.kt b/vector/src/main/java/im/vector/app/features/pin/PinCodeStore.kt index d3632edbca..d067503158 100644 --- a/vector/src/main/java/im/vector/app/features/pin/PinCodeStore.kt +++ b/vector/src/main/java/im/vector/app/features/pin/PinCodeStore.kt @@ -44,17 +44,17 @@ interface PinCodeStore { fun getRemainingBiometricsAttemptsNumber(): Int /** - * Will return the number of remaining attempts + * Will return the number of remaining attempts. */ fun onWrongPin(): Int /** - * Will return the number of remaining attempts + * Will return the number of remaining attempts. */ fun onWrongBiometrics(): Int /** - * Will reset the counters + * Will reset the counters. */ fun resetCounters() diff --git a/vector/src/main/java/im/vector/app/features/pin/PinLocker.kt b/vector/src/main/java/im/vector/app/features/pin/PinLocker.kt index f848a3174e..e41ea320ac 100644 --- a/vector/src/main/java/im/vector/app/features/pin/PinLocker.kt +++ b/vector/src/main/java/im/vector/app/features/pin/PinLocker.kt @@ -22,6 +22,7 @@ import androidx.lifecycle.LifecycleOwner import androidx.lifecycle.LiveData import androidx.lifecycle.MutableLiveData import im.vector.app.features.settings.VectorPreferences +import kotlinx.coroutines.DelicateCoroutinesApi import kotlinx.coroutines.GlobalScope import kotlinx.coroutines.launch import timber.log.Timber @@ -59,7 +60,7 @@ class PinLocker @Inject constructor( return liveState } - @Suppress("EXPERIMENTAL_API_USAGE") + @OptIn(DelicateCoroutinesApi::class) private fun computeState() { GlobalScope.launch { val state = if (shouldBeLocked && pinCodeStore.hasEncodedPin()) { diff --git a/vector/src/main/java/im/vector/app/features/poll/create/CreatePollViewModel.kt b/vector/src/main/java/im/vector/app/features/poll/create/CreatePollViewModel.kt index 835f6d670e..8bbbea6443 100644 --- a/vector/src/main/java/im/vector/app/features/poll/create/CreatePollViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/poll/create/CreatePollViewModel.kt @@ -25,6 +25,8 @@ import im.vector.app.core.di.hiltMavericksViewModelFactory import im.vector.app.core.platform.VectorViewModel import im.vector.app.features.poll.PollMode import org.matrix.android.sdk.api.session.Session +import org.matrix.android.sdk.api.session.getRoom +import org.matrix.android.sdk.api.session.room.getTimelineEvent import org.matrix.android.sdk.api.session.room.model.message.MessagePollContent import org.matrix.android.sdk.api.session.room.model.message.PollType import org.matrix.android.sdk.api.session.room.timeline.getLastMessageContent @@ -108,7 +110,7 @@ class CreatePollViewModel @AssistedInject constructor( } else -> { when (state.mode) { - PollMode.CREATE -> room.sendPoll(state.pollType, state.question, nonEmptyOptions) + PollMode.CREATE -> room.sendService().sendPoll(state.pollType, state.question, nonEmptyOptions) PollMode.EDIT -> sendEditedPoll(state.editedEventId!!, state.pollType, state.question, nonEmptyOptions) } _viewEvents.post(CreatePollViewEvents.Success) @@ -118,7 +120,7 @@ class CreatePollViewModel @AssistedInject constructor( private fun sendEditedPoll(editedEventId: String, pollType: PollType, question: String, options: List) { val editedEvent = room.getTimelineEvent(editedEventId) ?: return - room.editPoll(editedEvent, pollType, question, options) + room.relationService().editPoll(editedEvent, pollType, question, options) } private fun handleOnAddOption() { diff --git a/vector/src/main/java/im/vector/app/features/poll/create/CreatePollViewState.kt b/vector/src/main/java/im/vector/app/features/poll/create/CreatePollViewState.kt index 3fd8ab605c..cfa1ff847e 100644 --- a/vector/src/main/java/im/vector/app/features/poll/create/CreatePollViewState.kt +++ b/vector/src/main/java/im/vector/app/features/poll/create/CreatePollViewState.kt @@ -21,14 +21,14 @@ import im.vector.app.features.poll.PollMode import org.matrix.android.sdk.api.session.room.model.message.PollType data class CreatePollViewState( - val roomId: String, - val editedEventId: String?, - val mode: PollMode, - val question: String = "", - val options: List = List(CreatePollViewModel.MIN_OPTIONS_COUNT) { "" }, - val canCreatePoll: Boolean = false, - val canAddMoreOptions: Boolean = true, - val pollType: PollType = PollType.DISCLOSED_UNSTABLE + val roomId: String, + val editedEventId: String?, + val mode: PollMode, + val question: String = "", + val options: List = List(CreatePollViewModel.MIN_OPTIONS_COUNT) { "" }, + val canCreatePoll: Boolean = false, + val canAddMoreOptions: Boolean = true, + val pollType: PollType = PollType.DISCLOSED_UNSTABLE ) : MavericksState { constructor(args: CreatePollArgs) : this( diff --git a/vector/src/main/java/im/vector/app/features/popup/PopupAlertManager.kt b/vector/src/main/java/im/vector/app/features/popup/PopupAlertManager.kt index ae03b5345a..3ebcb3f318 100644 --- a/vector/src/main/java/im/vector/app/features/popup/PopupAlertManager.kt +++ b/vector/src/main/java/im/vector/app/features/popup/PopupAlertManager.kt @@ -24,7 +24,8 @@ import android.view.WindowInsetsController.APPEARANCE_LIGHT_STATUS_BARS import com.tapadoo.alerter.Alerter import im.vector.app.R import im.vector.app.core.platform.VectorBaseActivity -import im.vector.app.core.utils.isAnimationDisabled +import im.vector.app.core.time.Clock +import im.vector.app.core.utils.isAnimationEnabled import im.vector.app.features.analytics.ui.consent.AnalyticsOptInActivity import im.vector.app.features.pin.PinActivity import im.vector.app.features.signout.hard.SignedOutActivity @@ -41,7 +42,9 @@ import javax.inject.Singleton * will be back in the queue in first position. */ @Singleton -class PopupAlertManager @Inject constructor() { +class PopupAlertManager @Inject constructor( + private val clock: Clock, +) { companion object { const val INCOMING_CALL_PRIORITY = Int.MAX_VALUE @@ -87,7 +90,7 @@ class PopupAlertManager @Inject constructor() { } /** - * Cancel all alerts, after a sign out for instance + * Cancel all alerts, after a sign out for instance. */ fun cancelAll() { synchronized(alertQueue) { @@ -116,7 +119,7 @@ class PopupAlertManager @Inject constructor() { return } if (currentAlerter != null) { - if (currentAlerter!!.expirationTimestamp != null && System.currentTimeMillis() > currentAlerter!!.expirationTimestamp!!) { + if (currentAlerter!!.expirationTimestamp != null && clock.epochMillis() > currentAlerter!!.expirationTimestamp!!) { // this alert has expired, remove it // perform dismiss try { @@ -162,7 +165,7 @@ class PopupAlertManager @Inject constructor() { currentAlerter = next next?.let { if (!shouldBeDisplayedIn(next, currentActivity)) return - val currentTime = System.currentTimeMillis() + val currentTime = clock.epochMillis() if (next.expirationTimestamp != null && currentTime > next.expirationTimestamp!!) { // skip try { @@ -215,7 +218,7 @@ class PopupAlertManager @Inject constructor() { if (!alert.isLight) { clearLightStatusBar() } - val noAnimation = !animate || isAnimationDisabled(activity) + val noAnimation = !(animate && activity.isAnimationEnabled()) alert.weakCurrentActivity = WeakReference(activity) val alerter = Alerter.create(activity, alert.layoutRes) diff --git a/vector/src/main/java/im/vector/app/features/popup/VectorAlert.kt b/vector/src/main/java/im/vector/app/features/popup/VectorAlert.kt index 8b855fa542..ffdba8e04d 100644 --- a/vector/src/main/java/im/vector/app/features/popup/VectorAlert.kt +++ b/vector/src/main/java/im/vector/app/features/popup/VectorAlert.kt @@ -46,7 +46,7 @@ interface VectorAlert { var contentAction: Runnable? var dismissedAction: Runnable? - /** If this timestamp is after current time, this alert will be skipped */ + /** If this timestamp is after current time, this alert will be skipped. */ var expirationTimestamp: Long? fun addButton(title: String, action: Runnable, autoClose: Boolean = true) { @@ -77,7 +77,7 @@ open class DefaultVectorAlert( override val description: String, @DrawableRes override val iconId: Int?, /** - * Alert are displayed by default, but let this lambda return false to prevent displaying + * Alert are displayed by default, but let this lambda return false to prevent displaying. */ override val shouldBeDisplayedIn: ((Activity) -> Boolean) = { true } ) : VectorAlert { @@ -90,7 +90,7 @@ open class DefaultVectorAlert( override var contentAction: Runnable? = null override var dismissedAction: Runnable? = null - /** If this timestamp is after current time, this alert will be skipped */ + /** If this timestamp is after current time, this alert will be skipped. */ override var expirationTimestamp: Long? = null @LayoutRes diff --git a/vector/src/main/java/im/vector/app/features/popup/VerificationVectorAlert.kt b/vector/src/main/java/im/vector/app/features/popup/VerificationVectorAlert.kt index 228c629161..d02d5dbfe8 100644 --- a/vector/src/main/java/im/vector/app/features/popup/VerificationVectorAlert.kt +++ b/vector/src/main/java/im/vector/app/features/popup/VerificationVectorAlert.kt @@ -30,7 +30,7 @@ class VerificationVectorAlert(uid: String, override val description: String, @DrawableRes override val iconId: Int?, /** - * Alert are displayed by default, but let this lambda return false to prevent displaying + * Alert are displayed by default, but let this lambda return false to prevent displaying. */ override val shouldBeDisplayedIn: ((Activity) -> Boolean) = { true } ) : DefaultVectorAlert(uid, title, description, iconId, shouldBeDisplayedIn) { diff --git a/vector/src/main/java/im/vector/app/features/qrcode/QrCodeScannerActivity.kt b/vector/src/main/java/im/vector/app/features/qrcode/QrCodeScannerActivity.kt index b23f2f171d..7af7db27cf 100644 --- a/vector/src/main/java/im/vector/app/features/qrcode/QrCodeScannerActivity.kt +++ b/vector/src/main/java/im/vector/app/features/qrcode/QrCodeScannerActivity.kt @@ -29,7 +29,7 @@ import im.vector.app.core.platform.VectorBaseActivity import im.vector.app.databinding.ActivitySimpleBinding @AndroidEntryPoint -class QrCodeScannerActivity() : VectorBaseActivity() { +class QrCodeScannerActivity : VectorBaseActivity() { override fun getBinding() = ActivitySimpleBinding.inflate(layoutInflater) diff --git a/vector/src/main/java/im/vector/app/features/rageshake/BugReportActivity.kt b/vector/src/main/java/im/vector/app/features/rageshake/BugReportActivity.kt index 701d04f3c9..9dde7e071c 100755 --- a/vector/src/main/java/im/vector/app/features/rageshake/BugReportActivity.kt +++ b/vector/src/main/java/im/vector/app/features/rageshake/BugReportActivity.kt @@ -33,7 +33,7 @@ import org.matrix.android.sdk.api.extensions.tryOrNull import timber.log.Timber /** - * Form to send a bug report + * Form to send a bug report. */ @AndroidEntryPoint class BugReportActivity : VectorBaseActivity() { @@ -63,11 +63,11 @@ class BugReportActivity : VectorBaseActivity() { // Default screen is for bug report, so modify it for suggestion when (reportType) { - ReportType.BUG_REPORT -> { + ReportType.BUG_REPORT -> { supportActionBar?.setTitle(R.string.title_activity_bug_report) views.bugReportButtonContactMe.isVisible = true } - ReportType.SUGGESTION -> { + ReportType.SUGGESTION -> { supportActionBar?.setTitle(R.string.send_suggestion) views.bugReportFirstText.setText(R.string.send_suggestion_content) @@ -76,7 +76,7 @@ class BugReportActivity : VectorBaseActivity() { hideBugReportOptions() } - ReportType.SPACE_BETA_FEEDBACK -> { + ReportType.SPACE_BETA_FEEDBACK -> { supportActionBar?.setTitle(R.string.send_feedback_space_title) views.bugReportFirstText.setText(R.string.send_feedback_space_info) @@ -94,7 +94,7 @@ class BugReportActivity : VectorBaseActivity() { hideBugReportOptions() } - else -> { + else -> { // other types not supported here } } @@ -146,7 +146,7 @@ class BugReportActivity : VectorBaseActivity() { } /** - * Send the bug report + * Send the bug report. */ private fun sendBugReport() = withState(viewModel) { state -> views.bugReportScrollview.alpha = 0.3f @@ -176,16 +176,22 @@ class BugReportActivity : VectorBaseActivity() { if (!reason.isNullOrEmpty()) { when (reportType) { ReportType.BUG_REPORT -> { - Toast.makeText(this@BugReportActivity, - getString(R.string.send_bug_report_failed, reason), Toast.LENGTH_LONG).show() + Toast.makeText( + this@BugReportActivity, + getString(R.string.send_bug_report_failed, reason), Toast.LENGTH_LONG + ).show() } ReportType.SUGGESTION -> { - Toast.makeText(this@BugReportActivity, - getString(R.string.send_suggestion_failed, reason), Toast.LENGTH_LONG).show() + Toast.makeText( + this@BugReportActivity, + getString(R.string.send_suggestion_failed, reason), Toast.LENGTH_LONG + ).show() } ReportType.SPACE_BETA_FEEDBACK -> { - Toast.makeText(this@BugReportActivity, - getString(R.string.feedback_failed, reason), Toast.LENGTH_LONG).show() + Toast.makeText( + this@BugReportActivity, + getString(R.string.feedback_failed, reason), Toast.LENGTH_LONG + ).show() } else -> { // nop diff --git a/vector/src/main/java/im/vector/app/features/rageshake/BugReportViewModel.kt b/vector/src/main/java/im/vector/app/features/rageshake/BugReportViewModel.kt index d0a1280868..6437f5c8f8 100644 --- a/vector/src/main/java/im/vector/app/features/rageshake/BugReportViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/rageshake/BugReportViewModel.kt @@ -39,7 +39,7 @@ class BugReportViewModel @AssistedInject constructor( override fun create(initialState: BugReportState): BugReportViewModel } - companion object : MavericksViewModelFactory by hiltMavericksViewModelFactory() + companion object : MavericksViewModelFactory by hiltMavericksViewModelFactory() init { fetchHomeserverVersion() diff --git a/vector/src/main/java/im/vector/app/features/rageshake/BugReporter.kt b/vector/src/main/java/im/vector/app/features/rageshake/BugReporter.kt index 8b514f4003..b7ce7ffdb4 100755 --- a/vector/src/main/java/im/vector/app/features/rageshake/BugReporter.kt +++ b/vector/src/main/java/im/vector/app/features/rageshake/BugReporter.kt @@ -102,7 +102,7 @@ class BugReporter @Inject constructor( .adapter(Types.newParameterizedType(Map::class.java, String::class.java, Any::class.java)) /** - * Get current Screenshot + * Get current Screenshot. * * @return screenshot or null if not available */ @@ -111,7 +111,8 @@ class BugReporter @Inject constructor( private val coroutineScope = CoroutineScope(SupervisorJob() + Dispatchers.Main) - private val LOGCAT_CMD_ERROR = arrayOf("logcat", // /< Run 'logcat' command + private val LOGCAT_CMD_ERROR = arrayOf( + "logcat", // /< Run 'logcat' command "-d", // /< Dump the log rather than continue outputting it "-v", // formatting "threadtime", // include timestamps @@ -124,11 +125,11 @@ class BugReporter @Inject constructor( private val LOGCAT_CMD_DEBUG = arrayOf("logcat", "-d", "-v", "threadtime", "*:*") /** - * Bug report upload listener + * Bug report upload listener. */ interface IMXBugReportListener { /** - * The bug report has been cancelled + * The bug report has been cancelled. */ fun onUploadCancelled() @@ -140,7 +141,7 @@ class BugReporter @Inject constructor( fun onUploadFailed(reason: String?) /** - * The upload progress (in percent) + * The upload progress (in percent). * * @param progress the upload progress */ @@ -278,8 +279,10 @@ class BugReporter @Inject constructor( .addFormDataPart("device", Build.MODEL.trim()) .addFormDataPart("verbose_log", vectorPreferences.labAllowedExtendedLogging().toOnOff()) .addFormDataPart("multi_window", inMultiWindowMode.toOnOff()) - .addFormDataPart("os", Build.VERSION.RELEASE + " (API " + Build.VERSION.SDK_INT + ") " + - Build.VERSION.INCREMENTAL + "-" + Build.VERSION.CODENAME) + .addFormDataPart( + "os", Build.VERSION.RELEASE + " (API " + Build.VERSION.SDK_INT + ") " + + Build.VERSION.INCREMENTAL + "-" + Build.VERSION.CODENAME + ) .addFormDataPart("locale", Locale.getDefault().toString()) .addFormDataPart("app_language", VectorLocale.applicationLocale.toString()) .addFormDataPart("default_app_language", systemLocaleProvider.getSystemLocale().toString()) @@ -317,8 +320,10 @@ class BugReporter @Inject constructor( bitmap.compress(Bitmap.CompressFormat.PNG, 100, it) } - builder.addFormDataPart("file", - logCatScreenshotFile.name, logCatScreenshotFile.asRequestBody(MimeTypes.OctetStream.toMediaTypeOrNull())) + builder.addFormDataPart( + "file", + logCatScreenshotFile.name, logCatScreenshotFile.asRequestBody(MimeTypes.OctetStream.toMediaTypeOrNull()) + ) } catch (e: Exception) { Timber.e(e, "## sendBugReport() : fail to write screenshot$e") } @@ -494,18 +499,20 @@ class BugReporter @Inject constructor( // app: Identifier for the application (eg 'riot-web'). // Should correspond to a mapping configured in the configuration file for github issue reporting to work. // (see R.string.bug_report_url for configured RS server) - return context.getString(when (reportType) { - ReportType.AUTO_UISI_SENDER, - ReportType.AUTO_UISI -> R.string.bug_report_auto_uisi_app_name - else -> R.string.bug_report_app_name - }) + return context.getString( + when (reportType) { + ReportType.AUTO_UISI_SENDER, + ReportType.AUTO_UISI -> R.string.bug_report_auto_uisi_app_name + else -> R.string.bug_report_app_name + } + ) } // ============================================================================================================== // crash report management // ============================================================================================================== /** - * Provides the crash file + * Provides the crash file. * * @return the crash file */ @@ -514,7 +521,7 @@ class BugReporter @Inject constructor( } /** - * Remove the crash file + * Remove the crash file. */ fun deleteCrashFile() { val crashFile = getCrashFile() @@ -528,7 +535,7 @@ class BugReporter @Inject constructor( } /** - * Save the crash report + * Save the crash report. * * @param crashDescription teh crash description */ @@ -641,7 +648,7 @@ class BugReporter @Inject constructor( // ============================================================================================================== /** - * Save the logcat + * Save the logcat. * * @param isErrorLogcat true to save the error logcat * @return the file if the operation succeeds @@ -669,7 +676,7 @@ class BugReporter @Inject constructor( } /** - * Retrieves the logs + * Retrieves the logs. * * @param streamWriter the stream writer * @param isErrorLogCat true to save the error logs @@ -702,7 +709,7 @@ class BugReporter @Inject constructor( // ============================================================================================================== /** - * GZip a file + * GZip a file. * * @param fin the input file * @return the gzipped file diff --git a/vector/src/main/java/im/vector/app/features/rageshake/RageShake.kt b/vector/src/main/java/im/vector/app/features/rageshake/RageShake.kt index b5dd3f1ef0..ce738b85b9 100644 --- a/vector/src/main/java/im/vector/app/features/rageshake/RageShake.kt +++ b/vector/src/main/java/im/vector/app/features/rageshake/RageShake.kt @@ -105,7 +105,7 @@ class RageShake @Inject constructor(private val activity: FragmentActivity, companion object { /** - * Check if the feature is available + * Check if the feature is available. */ fun isAvailable(context: Context): Boolean { return context.getSystemService()?.getDefaultSensor(Sensor.TYPE_ACCELEROMETER) != null diff --git a/vector/src/main/java/im/vector/app/features/rageshake/VectorFileLogger.kt b/vector/src/main/java/im/vector/app/features/rageshake/VectorFileLogger.kt index 2f8e013f46..f16db4a66d 100644 --- a/vector/src/main/java/im/vector/app/features/rageshake/VectorFileLogger.kt +++ b/vector/src/main/java/im/vector/app/features/rageshake/VectorFileLogger.kt @@ -19,6 +19,7 @@ package im.vector.app.features.rageshake import android.content.Context import android.util.Log import im.vector.app.features.settings.VectorPreferences +import kotlinx.coroutines.DelicateCoroutinesApi import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.GlobalScope import kotlinx.coroutines.launch @@ -88,7 +89,7 @@ class VectorFileLogger @Inject constructor( } } - @Suppress("EXPERIMENTAL_API_USAGE") + @OptIn(DelicateCoroutinesApi::class) override fun log(priority: Int, tag: String?, message: String, t: Throwable?) { fileHandler ?: return GlobalScope.launch(Dispatchers.IO) { @@ -128,7 +129,7 @@ class VectorFileLogger @Inject constructor( } /** - * Log an Throwable + * Log an Throwable. * * @param throwable the throwable to log */ diff --git a/vector/src/main/java/im/vector/app/features/rageshake/VectorUncaughtExceptionHandler.kt b/vector/src/main/java/im/vector/app/features/rageshake/VectorUncaughtExceptionHandler.kt index bd2f0b67bd..bc78b84088 100644 --- a/vector/src/main/java/im/vector/app/features/rageshake/VectorUncaughtExceptionHandler.kt +++ b/vector/src/main/java/im/vector/app/features/rageshake/VectorUncaughtExceptionHandler.kt @@ -47,7 +47,7 @@ class VectorUncaughtExceptionHandler @Inject constructor( private val preferences = DefaultSharedPreferences.getInstance(context) /** - * Activate this handler + * Activate this handler. */ fun activate() { previousHandler = Thread.getDefaultUncaughtExceptionHandler() @@ -55,12 +55,13 @@ class VectorUncaughtExceptionHandler @Inject constructor( } /** - * An uncaught exception has been triggered + * An uncaught exception has been triggered. * * @param thread the thread * @param throwable the throwable * @return the exception description */ + @Suppress("PrintStackTrace") override fun uncaughtException(thread: Thread, throwable: Throwable) { Timber.v("Uncaught exception: $throwable") preferences.edit(commit = true) { @@ -112,7 +113,7 @@ class VectorUncaughtExceptionHandler @Inject constructor( } /** - * Tells if the application crashed + * Tells if the application crashed. * * @return true if the application crashed */ @@ -121,7 +122,7 @@ class VectorUncaughtExceptionHandler @Inject constructor( } /** - * Clear the crash status + * Clear the crash status. */ fun clearAppCrashStatus() { preferences.edit { diff --git a/vector/src/main/java/im/vector/app/features/raw/wellknown/ElementWellKnown.kt b/vector/src/main/java/im/vector/app/features/raw/wellknown/ElementWellKnown.kt index 91b0f4d2f7..c451c35f20 100644 --- a/vector/src/main/java/im/vector/app/features/raw/wellknown/ElementWellKnown.kt +++ b/vector/src/main/java/im/vector/app/features/raw/wellknown/ElementWellKnown.kt @@ -22,7 +22,7 @@ import com.squareup.moshi.JsonClass @JsonClass(generateAdapter = true) data class ElementWellKnown( /** - * Preferred Jitsi domain + * Preferred Jitsi domain. */ @Json(name = "im.vector.riot.jitsi") val jitsiServer: WellKnownPreferredConfig? = null, diff --git a/vector/src/main/java/im/vector/app/features/reactions/EmojiReactionPickerActivity.kt b/vector/src/main/java/im/vector/app/features/reactions/EmojiReactionPickerActivity.kt index 7062a5d02d..f47b5b2411 100644 --- a/vector/src/main/java/im/vector/app/features/reactions/EmojiReactionPickerActivity.kt +++ b/vector/src/main/java/im/vector/app/features/reactions/EmojiReactionPickerActivity.kt @@ -44,8 +44,8 @@ import javax.inject.Inject /** * - * TODO: Loading indicator while getting emoji data source? - * TODO: Finish Refactor to vector base activity + * TODO Loading indicator while getting emoji data source? + * TODO Finish Refactor to vector base activity */ @AndroidEntryPoint class EmojiReactionPickerActivity : VectorBaseActivity(), diff --git a/vector/src/main/java/im/vector/app/features/reactions/EmojiRecyclerAdapter.kt b/vector/src/main/java/im/vector/app/features/reactions/EmojiRecyclerAdapter.kt index 5c0d2f36f5..8b04e9cfd6 100644 --- a/vector/src/main/java/im/vector/app/features/reactions/EmojiRecyclerAdapter.kt +++ b/vector/src/main/java/im/vector/app/features/reactions/EmojiRecyclerAdapter.kt @@ -39,11 +39,10 @@ import kotlinx.coroutines.launch import javax.inject.Inject import kotlin.math.abs -/** - * - * TODO: Configure Span using available width and emoji size - * TODO: Performances - * TODO: Scroll to section - Find a way to snap section to the top +/* + * TODO Configure Span using available width and emoji size + * TODO Performances + * TODO Scroll to section - Find a way to snap section to the top */ class EmojiRecyclerAdapter @Inject constructor() : RecyclerView.Adapter() { diff --git a/vector/src/main/java/im/vector/app/features/reactions/EmojiSearchResultViewModel.kt b/vector/src/main/java/im/vector/app/features/reactions/EmojiSearchResultViewModel.kt index e1e52fdca1..720061e326 100644 --- a/vector/src/main/java/im/vector/app/features/reactions/EmojiSearchResultViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/reactions/EmojiSearchResultViewModel.kt @@ -36,7 +36,7 @@ data class EmojiSearchResultViewState( class EmojiSearchResultViewModel @AssistedInject constructor( @Assisted initialState: EmojiSearchResultViewState, private val dataSource: EmojiDataSource) : - VectorViewModel(initialState) { + VectorViewModel(initialState) { @AssistedFactory interface Factory : MavericksAssistedViewModelFactory { diff --git a/vector/src/main/java/im/vector/app/features/reactions/data/EmojiItem.kt b/vector/src/main/java/im/vector/app/features/reactions/data/EmojiItem.kt index 5a532c78f1..ed9aff4176 100644 --- a/vector/src/main/java/im/vector/app/features/reactions/data/EmojiItem.kt +++ b/vector/src/main/java/im/vector/app/features/reactions/data/EmojiItem.kt @@ -20,6 +20,7 @@ import com.squareup.moshi.Json import com.squareup.moshi.JsonClass /** + * Example: * name: 'a', * unified: 'b', * non_qualified: 'c', @@ -34,7 +35,7 @@ import com.squareup.moshi.JsonClass * emoticons: 'l', * text: 'm', * short_names: 'n', - * added_in: 'o' + * added_in: 'o'. */ @JsonClass(generateAdapter = true) data class EmojiItem( diff --git a/vector/src/main/java/im/vector/app/features/reactions/widget/DotsView.kt b/vector/src/main/java/im/vector/app/features/reactions/widget/DotsView.kt index c9283c663a..7b0052db0b 100644 --- a/vector/src/main/java/im/vector/app/features/reactions/widget/DotsView.kt +++ b/vector/src/main/java/im/vector/app/features/reactions/widget/DotsView.kt @@ -126,10 +126,12 @@ class DotsView @JvmOverloads constructor(context: Context, attrs: AttributeSet? this.currentDotSize2 = maxDotSize } else if (currentProgress < 0.5) { this.currentDotSize2 = CircleView.mapValueFromRangeToRange( - currentProgress, 0.2f, 0.5f, maxDotSize, 0.3f * maxDotSize) + currentProgress, 0.2f, 0.5f, maxDotSize, 0.3f * maxDotSize + ) } else { this.currentDotSize2 = CircleView.mapValueFromRangeToRange( - currentProgress, 0.5f, 1f, maxDotSize * 0.3f, 0f) + currentProgress, 0.5f, 1f, maxDotSize * 0.3f, 0f + ) } } @@ -143,17 +145,20 @@ class DotsView @JvmOverloads constructor(context: Context, attrs: AttributeSet? private fun updateOuterDotsPosition() { if (currentProgress < 0.3f) { this.currentRadius1 = CircleView.mapValueFromRangeToRange( - currentProgress, 0.0f, 0.3f, 0f, maxOuterDotsRadius * 0.8f) + currentProgress, 0.0f, 0.3f, 0f, maxOuterDotsRadius * 0.8f + ) } else { this.currentRadius1 = CircleView.mapValueFromRangeToRange( - currentProgress, 0.3f, 1f, 0.8f * maxOuterDotsRadius, maxOuterDotsRadius) + currentProgress, 0.3f, 1f, 0.8f * maxOuterDotsRadius, maxOuterDotsRadius + ) } if (currentProgress < 0.7) { this.currentDotSize1 = maxDotSize } else { this.currentDotSize1 = CircleView.mapValueFromRangeToRange( - currentProgress, 0.7f, 1f, maxDotSize, 0f) + currentProgress, 0.7f, 1f, maxDotSize, 0f + ) } } diff --git a/vector/src/main/java/im/vector/app/features/room/RequireActiveMembershipViewModel.kt b/vector/src/main/java/im/vector/app/features/room/RequireActiveMembershipViewModel.kt index 0cb49746f1..0cd289cf64 100644 --- a/vector/src/main/java/im/vector/app/features/room/RequireActiveMembershipViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/room/RequireActiveMembershipViewModel.kt @@ -36,7 +36,9 @@ import kotlinx.coroutines.flow.onEach import org.matrix.android.sdk.api.query.QueryStringValue import org.matrix.android.sdk.api.session.Session import org.matrix.android.sdk.api.session.events.model.EventType +import org.matrix.android.sdk.api.session.getRoom import org.matrix.android.sdk.api.session.room.Room +import org.matrix.android.sdk.api.session.room.getStateEvent import org.matrix.android.sdk.api.session.room.model.Membership import org.matrix.android.sdk.api.session.room.model.RoomSummary import org.matrix.android.sdk.api.util.Optional @@ -44,13 +46,13 @@ import org.matrix.android.sdk.flow.flow import org.matrix.android.sdk.flow.unwrap /** - * This ViewModel observe a room summary and notify when the room is left + * This ViewModel observe a room summary and notify when the room is left. */ class RequireActiveMembershipViewModel @AssistedInject constructor( @Assisted initialState: RequireActiveMembershipViewState, private val stringProvider: StringProvider, private val session: Session) : - VectorViewModel(initialState) { + VectorViewModel(initialState) { @AssistedFactory interface Factory : MavericksAssistedViewModelFactory { @@ -92,7 +94,7 @@ class RequireActiveMembershipViewModel @AssistedInject constructor( } val senderId = room.getStateEvent(EventType.STATE_ROOM_MEMBER, QueryStringValue.Equals(session.myUserId))?.senderId val senderDisplayName = senderId?.takeIf { it != session.myUserId }?.let { - room.getRoomMember(it)?.displayName ?: it + room.membershipService().getRoomMember(it)?.displayName ?: it } val viewEvent = when (roomSummary.membership) { Membership.LEAVE -> { diff --git a/vector/src/main/java/im/vector/app/features/roomdirectory/ExplicitTermFilter.kt b/vector/src/main/java/im/vector/app/features/roomdirectory/ExplicitTermFilter.kt index e184e77557..b1876d2c4e 100644 --- a/vector/src/main/java/im/vector/app/features/roomdirectory/ExplicitTermFilter.kt +++ b/vector/src/main/java/im/vector/app/features/roomdirectory/ExplicitTermFilter.kt @@ -35,7 +35,7 @@ class ExplicitTermFilter @Inject constructor( .toRegex(RegexOption.IGNORE_CASE) fun canSearchFor(term: String): Boolean { - return term !in explicitTerms && term != "18+" + return term !in explicitTerms && term != "18+" } fun isValid(str: String): Boolean { diff --git a/vector/src/main/java/im/vector/app/features/roomdirectory/JoinState.kt b/vector/src/main/java/im/vector/app/features/roomdirectory/JoinState.kt index 12c264aa65..16c8491b2a 100644 --- a/vector/src/main/java/im/vector/app/features/roomdirectory/JoinState.kt +++ b/vector/src/main/java/im/vector/app/features/roomdirectory/JoinState.kt @@ -17,7 +17,7 @@ package im.vector.app.features.roomdirectory /** - * Join state of a room + * Join state of a room. */ enum class JoinState { NOT_JOINED, diff --git a/vector/src/main/java/im/vector/app/features/roomdirectory/PublicRoomsFragment.kt b/vector/src/main/java/im/vector/app/features/roomdirectory/PublicRoomsFragment.kt index b8bba347fd..9562c3b504 100644 --- a/vector/src/main/java/im/vector/app/features/roomdirectory/PublicRoomsFragment.kt +++ b/vector/src/main/java/im/vector/app/features/roomdirectory/PublicRoomsFragment.kt @@ -33,6 +33,7 @@ import im.vector.app.core.platform.VectorBaseFragment import im.vector.app.core.platform.showOptimizedSnackbar import im.vector.app.core.utils.toast import im.vector.app.databinding.FragmentPublicRoomsBinding +import im.vector.app.features.analytics.plan.ViewRoom import im.vector.app.features.permalink.NavigationInterceptor import im.vector.app.features.permalink.PermalinkHandler import kotlinx.coroutines.flow.debounce @@ -47,7 +48,7 @@ import javax.inject.Inject /** * What can be improved: - * - When filtering more (when entering new chars), we could filter on result we already have, during the new server request, to avoid empty screen effect + * - When filtering more (when entering new chars), we could filter on result we already have, during the new server request, to avoid empty screen effect. */ class PublicRoomsFragment @Inject constructor( private val publicRoomsController: PublicRoomsController, @@ -143,7 +144,11 @@ class PublicRoomsFragment @Inject constructor( withState(viewModel) { state -> when (joinState) { JoinState.JOINED -> { - navigator.openRoom(requireActivity(), publicRoom.roomId) + navigator.openRoom( + context = requireActivity(), + roomId = publicRoom.roomId, + trigger = ViewRoom.Trigger.RoomDirectory + ) } else -> { // ROOM PREVIEW diff --git a/vector/src/main/java/im/vector/app/features/roomdirectory/RoomDirectoryActivity.kt b/vector/src/main/java/im/vector/app/features/roomdirectory/RoomDirectoryActivity.kt index f0df31342e..afee8e2638 100644 --- a/vector/src/main/java/im/vector/app/features/roomdirectory/RoomDirectoryActivity.kt +++ b/vector/src/main/java/im/vector/app/features/roomdirectory/RoomDirectoryActivity.kt @@ -29,6 +29,7 @@ import im.vector.app.core.extensions.popBackstack import im.vector.app.core.platform.VectorBaseActivity import im.vector.app.databinding.ActivitySimpleBinding import im.vector.app.features.analytics.plan.MobileScreen +import im.vector.app.features.analytics.plan.ViewRoom import im.vector.app.features.matrixto.MatrixToBottomSheet import im.vector.app.features.navigation.Navigator import im.vector.app.features.roomdirectory.createroom.CreateRoomArgs @@ -88,8 +89,8 @@ class RoomDirectoryActivity : VectorBaseActivity(), Matri } } - override fun mxToBottomSheetNavigateToRoom(roomId: String) { - navigator.openRoom(this, roomId) + override fun mxToBottomSheetNavigateToRoom(roomId: String, trigger: ViewRoom.Trigger?) { + navigator.openRoom(this, roomId, trigger = trigger) } override fun mxToBottomSheetSwitchToSpace(spaceId: String) { diff --git a/vector/src/main/java/im/vector/app/features/roomdirectory/RoomDirectoryData.kt b/vector/src/main/java/im/vector/app/features/roomdirectory/RoomDirectoryData.kt index ae87de2bdf..c54d10f682 100644 --- a/vector/src/main/java/im/vector/app/features/roomdirectory/RoomDirectoryData.kt +++ b/vector/src/main/java/im/vector/app/features/roomdirectory/RoomDirectoryData.kt @@ -21,28 +21,28 @@ package im.vector.app.features.roomdirectory */ data class RoomDirectoryData( /** - * The server name (might be null) + * The server name (might be null). * Set null when the server is the current user's homeserver. */ val homeServer: String? = null, /** - * The display name (the server description) + * The display name (the server description). */ val displayName: String = MATRIX_PROTOCOL_NAME, /** - * the avatar url + * The avatar url. */ val avatarUrl: String? = null, /** - * The third party server identifier + * The third party server identifier. */ val thirdPartyInstanceId: String? = null, /** - * Tell if all the federated servers must be included + * Tell if all the federated servers must be included. */ val includeAllNetworks: Boolean = false ) { diff --git a/vector/src/main/java/im/vector/app/features/roomdirectory/RoomDirectoryServer.kt b/vector/src/main/java/im/vector/app/features/roomdirectory/RoomDirectoryServer.kt index 0f29ae5986..bea27df312 100644 --- a/vector/src/main/java/im/vector/app/features/roomdirectory/RoomDirectoryServer.kt +++ b/vector/src/main/java/im/vector/app/features/roomdirectory/RoomDirectoryServer.kt @@ -20,17 +20,17 @@ data class RoomDirectoryServer( val serverName: String, /** - * True if this is the current user server + * True if this is the current user server. */ val isUserServer: Boolean, /** - * True if manually added, so it can be removed by the user + * True if manually added, so it can be removed by the user. */ val isManuallyAdded: Boolean, /** - * Supported protocols + * Supported protocols. * TODO Rename RoomDirectoryData to RoomDirectoryProtocols */ val protocols: List diff --git a/vector/src/main/java/im/vector/app/features/roomdirectory/RoomDirectorySharedAction.kt b/vector/src/main/java/im/vector/app/features/roomdirectory/RoomDirectorySharedAction.kt index ea9211cc7b..fb3b619392 100644 --- a/vector/src/main/java/im/vector/app/features/roomdirectory/RoomDirectorySharedAction.kt +++ b/vector/src/main/java/im/vector/app/features/roomdirectory/RoomDirectorySharedAction.kt @@ -19,7 +19,7 @@ package im.vector.app.features.roomdirectory import im.vector.app.core.platform.VectorSharedAction /** - * Supported navigation actions for [RoomDirectoryActivity] + * Supported navigation actions for [RoomDirectoryActivity]. */ sealed class RoomDirectorySharedAction : VectorSharedAction { object Back : RoomDirectorySharedAction() diff --git a/vector/src/main/java/im/vector/app/features/roomdirectory/RoomDirectoryViewEvents.kt b/vector/src/main/java/im/vector/app/features/roomdirectory/RoomDirectoryViewEvents.kt index 60b1d11c4a..1805abc369 100644 --- a/vector/src/main/java/im/vector/app/features/roomdirectory/RoomDirectoryViewEvents.kt +++ b/vector/src/main/java/im/vector/app/features/roomdirectory/RoomDirectoryViewEvents.kt @@ -19,7 +19,7 @@ package im.vector.app.features.roomdirectory import im.vector.app.core.platform.VectorViewEvents /** - * Transient events for room directory screen + * Transient events for room directory screen. */ sealed class RoomDirectoryViewEvents : VectorViewEvents { data class Failure(val throwable: Throwable) : RoomDirectoryViewEvents() diff --git a/vector/src/main/java/im/vector/app/features/roomdirectory/RoomDirectoryViewModel.kt b/vector/src/main/java/im/vector/app/features/roomdirectory/RoomDirectoryViewModel.kt index 710d4d5b5f..b2bfc53f96 100644 --- a/vector/src/main/java/im/vector/app/features/roomdirectory/RoomDirectoryViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/roomdirectory/RoomDirectoryViewModel.kt @@ -29,6 +29,7 @@ import im.vector.app.core.di.hiltMavericksViewModelFactory import im.vector.app.core.platform.VectorViewModel import im.vector.app.features.analytics.AnalyticsTracker import im.vector.app.features.analytics.extensions.toAnalyticsJoinedRoom +import im.vector.app.features.analytics.plan.JoinedRoom import im.vector.app.features.settings.VectorPreferences import kotlinx.coroutines.CancellationException import kotlinx.coroutines.Job @@ -165,7 +166,8 @@ class RoomDirectoryViewModel @AssistedInject constructor( currentJob = viewModelScope.launch { val data = try { - session.getPublicRooms(roomDirectoryData.homeServer, + session.roomDirectoryService().getPublicRooms( + roomDirectoryData.homeServer, PublicRoomsParams( limit = PUBLIC_ROOMS_LIMIT, filter = PublicRoomsFilter(searchTerm = filter), @@ -225,8 +227,8 @@ class RoomDirectoryViewModel @AssistedInject constructor( val viaServers = listOfNotNull(state.roomDirectoryData.homeServer) viewModelScope.launch { try { - session.joinRoom(action.publicRoom.roomId, viaServers = viaServers) - analyticsTracker.capture(action.publicRoom.toAnalyticsJoinedRoom()) + session.roomService().joinRoom(action.publicRoom.roomId, viaServers = viaServers) + analyticsTracker.capture(action.publicRoom.toAnalyticsJoinedRoom(JoinedRoom.Trigger.RoomDirectory)) // We do not update the joiningRoomsIds here, because, the room is not joined yet regarding the sync data. // Instead, we wait for the room to be joined } catch (failure: Throwable) { diff --git a/vector/src/main/java/im/vector/app/features/roomdirectory/createroom/CreateRoomActivity.kt b/vector/src/main/java/im/vector/app/features/roomdirectory/createroom/CreateRoomActivity.kt index e4c350b88e..154c0bbb9d 100644 --- a/vector/src/main/java/im/vector/app/features/roomdirectory/createroom/CreateRoomActivity.kt +++ b/vector/src/main/java/im/vector/app/features/roomdirectory/createroom/CreateRoomActivity.kt @@ -33,7 +33,7 @@ import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.onEach /** - * Simple container for [CreateRoomFragment] + * Simple container for [CreateRoomFragment]. */ @AndroidEntryPoint class CreateRoomActivity : VectorBaseActivity() { @@ -87,12 +87,14 @@ class CreateRoomActivity : VectorBaseActivity() { openAfterCreate: Boolean = true, currentSpaceId: String? = null): Intent { return Intent(context, CreateRoomActivity::class.java).apply { - putExtra(Mavericks.KEY_ARG, CreateRoomArgs( + putExtra( + Mavericks.KEY_ARG, CreateRoomArgs( initialName = initialName, isSpace = isSpace, openAfterCreate = openAfterCreate, parentSpaceId = currentSpaceId - )) + ) + ) } } diff --git a/vector/src/main/java/im/vector/app/features/roomdirectory/createroom/CreateRoomController.kt b/vector/src/main/java/im/vector/app/features/roomdirectory/createroom/CreateRoomController.kt index 7dd9cb4e00..fae88ed8a2 100644 --- a/vector/src/main/java/im/vector/app/features/roomdirectory/createroom/CreateRoomController.kt +++ b/vector/src/main/java/im/vector/app/features/roomdirectory/createroom/CreateRoomController.kt @@ -146,7 +146,8 @@ class CreateRoomController @Inject constructor( hint(host.stringProvider.getString(R.string.room_alias_address_hint)) errorMessage( host.roomAliasErrorFormatter.format( - (((viewState.asyncCreateRoomRequest as? Fail)?.error) as? CreateRoomFailure.AliasError)?.aliasError) + (((viewState.asyncCreateRoomRequest as? Fail)?.error) as? CreateRoomFailure.AliasError)?.aliasError + ) ) onTextChange { value -> host.listener?.setAliasLocalPart(value) diff --git a/vector/src/main/java/im/vector/app/features/roomdirectory/createroom/CreateRoomFragment.kt b/vector/src/main/java/im/vector/app/features/roomdirectory/createroom/CreateRoomFragment.kt index 2871513c1f..82cc270458 100644 --- a/vector/src/main/java/im/vector/app/features/roomdirectory/createroom/CreateRoomFragment.kt +++ b/vector/src/main/java/im/vector/app/features/roomdirectory/createroom/CreateRoomFragment.kt @@ -37,7 +37,9 @@ import im.vector.app.core.extensions.configureWith import im.vector.app.core.platform.OnBackPressed import im.vector.app.core.platform.VectorBaseFragment import im.vector.app.core.resources.ColorProvider +import im.vector.app.core.time.Clock import im.vector.app.databinding.FragmentCreateRoomBinding +import im.vector.app.features.analytics.plan.ViewRoom import im.vector.app.features.navigation.Navigator import im.vector.app.features.roomdirectory.RoomDirectorySharedAction import im.vector.app.features.roomdirectory.RoomDirectorySharedActionViewModel @@ -62,7 +64,8 @@ data class CreateRoomArgs( class CreateRoomFragment @Inject constructor( private val createRoomController: CreateRoomController, private val createSpaceController: CreateSubSpaceController, - colorProvider: ColorProvider + colorProvider: ColorProvider, + clock: Clock, ) : VectorBaseFragment(), CreateRoomController.Listener, GalleryOrCameraDialogHelper.Listener, @@ -74,7 +77,7 @@ class CreateRoomFragment @Inject constructor( private lateinit var roomJoinRuleSharedActionViewModel: RoomJoinRuleSharedActionViewModel - private val galleryOrCameraDialogHelper = GalleryOrCameraDialogHelper(this, colorProvider) + private val galleryOrCameraDialogHelper = GalleryOrCameraDialogHelper(this, colorProvider, clock) override fun getBinding(inflater: LayoutInflater, container: ViewGroup?): FragmentCreateRoomBinding { return FragmentCreateRoomBinding.inflate(inflater, container, false) @@ -166,7 +169,8 @@ class CreateRoomFragment @Inject constructor( } else { listOf(RoomJoinRules.INVITE, RoomJoinRules.PUBLIC) } - RoomJoinRuleBottomSheet.newInstance(state.roomJoinRules, + RoomJoinRuleBottomSheet.newInstance( + state.roomJoinRules, allowed.map { it.toOption(false) }, state.isSubSpace, state.parentSpaceSummary?.displayName @@ -219,20 +223,25 @@ class CreateRoomFragment @Inject constructor( val async = state.asyncCreateRoomRequest views.waitingView.root.isVisible = async is Loading if (async is Success) { + val roomId = async() // Navigate to freshly created room if (state.openAfterCreate) { if (state.isSubSpace) { navigator.switchToSpace( requireContext(), - async(), + roomId, Navigator.PostSwitchSpaceAction.None ) } else { - navigator.openRoom(requireActivity(), async()) + navigator.openRoom( + context = requireActivity(), + roomId = roomId, + trigger = ViewRoom.Trigger.Created + ) } } - sharedActionViewModel.post(RoomDirectorySharedAction.CreateRoomSuccess(async())) + sharedActionViewModel.post(RoomDirectorySharedAction.CreateRoomSuccess(roomId)) sharedActionViewModel.post(RoomDirectorySharedAction.Close) } else { // Populate list with Epoxy diff --git a/vector/src/main/java/im/vector/app/features/roomdirectory/createroom/CreateRoomViewEvents.kt b/vector/src/main/java/im/vector/app/features/roomdirectory/createroom/CreateRoomViewEvents.kt index af745ce5ff..1964f9617e 100644 --- a/vector/src/main/java/im/vector/app/features/roomdirectory/createroom/CreateRoomViewEvents.kt +++ b/vector/src/main/java/im/vector/app/features/roomdirectory/createroom/CreateRoomViewEvents.kt @@ -19,7 +19,7 @@ package im.vector.app.features.roomdirectory.createroom import im.vector.app.core.platform.VectorViewEvents /** - * Transient events for room creation screen + * Transient events for room creation screen. */ sealed class CreateRoomViewEvents : VectorViewEvents { data class Failure(val throwable: Throwable) : CreateRoomViewEvents() diff --git a/vector/src/main/java/im/vector/app/features/roomdirectory/createroom/CreateRoomViewModel.kt b/vector/src/main/java/im/vector/app/features/roomdirectory/createroom/CreateRoomViewModel.kt index 7d65c44a57..1994de396f 100644 --- a/vector/src/main/java/im/vector/app/features/roomdirectory/createroom/CreateRoomViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/roomdirectory/createroom/CreateRoomViewModel.kt @@ -40,6 +40,7 @@ import org.matrix.android.sdk.api.extensions.orFalse import org.matrix.android.sdk.api.extensions.tryOrNull import org.matrix.android.sdk.api.raw.RawService import org.matrix.android.sdk.api.session.Session +import org.matrix.android.sdk.api.session.getRoomSummary import org.matrix.android.sdk.api.session.homeserver.HomeServerCapabilities import org.matrix.android.sdk.api.session.room.alias.RoomAliasError import org.matrix.android.sdk.api.session.room.failure.CreateRoomFailure @@ -74,7 +75,8 @@ class CreateRoomViewModel @AssistedInject constructor( val parentSpaceId = initialState.parentSpaceId ?: appStateHandler.safeActiveSpaceId() - val restrictedSupport = session.getHomeServerCapabilities().isFeatureSupported(HomeServerCapabilities.ROOM_CAP_RESTRICTED) + val restrictedSupport = session.homeServerCapabilitiesService().getHomeServerCapabilities() + .isFeatureSupported(HomeServerCapabilities.ROOM_CAP_RESTRICTED) val createRestricted = restrictedSupport == HomeServerCapabilities.RoomCapabilitySupport.SUPPORTED val defaultJoinRules = if (parentSpaceId != null && createRestricted) { @@ -265,7 +267,7 @@ class CreateRoomViewModel @AssistedInject constructor( RoomJoinRules.RESTRICTED -> { state.parentSpaceId?.let { featurePreset = RestrictedRoomPreset( - session.getHomeServerCapabilities(), + session.homeServerCapabilitiesService().getHomeServerCapabilities(), listOf(RoomJoinRulesAllowEntry.restrictedToRoom(state.parentSpaceId)) ) } @@ -295,9 +297,9 @@ class CreateRoomViewModel @AssistedInject constructor( } } - // TODO: Should this be non-cancellable? + // TODO Should this be non-cancellable? viewModelScope.launch { - runCatching { session.createRoom(createRoomParams) }.fold( + runCatching { session.roomService().createRoom(createRoomParams) }.fold( { roomId -> analyticsTracker.capture(CreatedRoom(isDM = createRoomParams.isDirect.orFalse())) if (state.parentSpaceId != null) { diff --git a/vector/src/main/java/im/vector/app/features/roomdirectory/createroom/CreateRoomViewState.kt b/vector/src/main/java/im/vector/app/features/roomdirectory/createroom/CreateRoomViewState.kt index cf8cc669ab..2a6ee3727f 100644 --- a/vector/src/main/java/im/vector/app/features/roomdirectory/createroom/CreateRoomViewState.kt +++ b/vector/src/main/java/im/vector/app/features/roomdirectory/createroom/CreateRoomViewState.kt @@ -51,7 +51,7 @@ data class CreateRoomViewState( ) /** - * Return true if there is not important input from user + * Return true if there is not important input from user. */ fun isEmpty() = avatarUri == null && roomName.isEmpty() && diff --git a/vector/src/main/java/im/vector/app/features/roomdirectory/createroom/CreateSubSpaceController.kt b/vector/src/main/java/im/vector/app/features/roomdirectory/createroom/CreateSubSpaceController.kt index e67b272c32..6d2fc15a0b 100644 --- a/vector/src/main/java/im/vector/app/features/roomdirectory/createroom/CreateSubSpaceController.kt +++ b/vector/src/main/java/im/vector/app/features/roomdirectory/createroom/CreateSubSpaceController.kt @@ -86,7 +86,8 @@ class CreateSubSpaceController @Inject constructor( maxLength(MatrixConstants.maxAliasLocalPartLength(data.homeServerName)) errorMessage( host.roomAliasErrorFormatter.format( - (((data.asyncCreateRoomRequest as? Fail)?.error) as? CreateRoomFailure.AliasError)?.aliasError) + (((data.asyncCreateRoomRequest as? Fail)?.error) as? CreateRoomFailure.AliasError)?.aliasError + ) ) onTextChange { value -> host.listener?.setAliasLocalPart(value) diff --git a/vector/src/main/java/im/vector/app/features/roomdirectory/picker/RoomDirectoryPickerViewModel.kt b/vector/src/main/java/im/vector/app/features/roomdirectory/picker/RoomDirectoryPickerViewModel.kt index 51af9a8286..48b6997bd4 100644 --- a/vector/src/main/java/im/vector/app/features/roomdirectory/picker/RoomDirectoryPickerViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/roomdirectory/picker/RoomDirectoryPickerViewModel.kt @@ -152,7 +152,7 @@ class RoomDirectoryPickerViewModel @AssistedInject constructor( copy(addServerAsync = Loading()) } try { - session.getPublicRooms( + session.roomDirectoryService().getPublicRooms( server = enteredServer, publicRoomsParams = PublicRoomsParams(limit = 1) ) diff --git a/vector/src/main/java/im/vector/app/features/roomdirectory/roompreview/RoomPreviewNoPreviewFragment.kt b/vector/src/main/java/im/vector/app/features/roomdirectory/roompreview/RoomPreviewNoPreviewFragment.kt index baa6d0add1..f197479692 100644 --- a/vector/src/main/java/im/vector/app/features/roomdirectory/roompreview/RoomPreviewNoPreviewFragment.kt +++ b/vector/src/main/java/im/vector/app/features/roomdirectory/roompreview/RoomPreviewNoPreviewFragment.kt @@ -38,6 +38,7 @@ import im.vector.app.core.utils.styleMatchingText import im.vector.app.core.utils.tappableMatchingText import im.vector.app.databinding.FragmentRoomPreviewNoPreviewBinding import im.vector.app.features.analytics.plan.MobileScreen +import im.vector.app.features.analytics.plan.ViewRoom import im.vector.app.features.home.AvatarRenderer import im.vector.app.features.navigation.Navigator import im.vector.app.features.roomdirectory.JoinState @@ -49,7 +50,7 @@ import org.matrix.android.sdk.api.util.MatrixItem import javax.inject.Inject /** - * Note: this Fragment is also used for world readable room for the moment + * Note: this Fragment is also used for world readable room for the moment. */ class RoomPreviewNoPreviewFragment @Inject constructor( private val avatarRenderer: AvatarRenderer @@ -100,7 +101,13 @@ class RoomPreviewNoPreviewFragment @Inject constructor( if (state.roomType == RoomType.SPACE) { navigator.switchToSpace(requireActivity(), state.roomId, Navigator.PostSwitchSpaceAction.None) } else { - navigator.openRoom(requireActivity(), state.roomId, roomPreviewData.eventId, roomPreviewData.buildTask) + navigator.openRoom( + context = requireActivity(), + roomId = state.roomId, + eventId = roomPreviewData.eventId, + buildTask = roomPreviewData.buildTask, + trigger = ViewRoom.Trigger.MobileRoomPreview + ) } } diff --git a/vector/src/main/java/im/vector/app/features/roomdirectory/roompreview/RoomPreviewViewModel.kt b/vector/src/main/java/im/vector/app/features/roomdirectory/roompreview/RoomPreviewViewModel.kt index 8eefd7659d..e58feef5b3 100644 --- a/vector/src/main/java/im/vector/app/features/roomdirectory/roompreview/RoomPreviewViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/roomdirectory/roompreview/RoomPreviewViewModel.kt @@ -27,7 +27,7 @@ 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.analytics.AnalyticsTracker -import im.vector.app.features.analytics.extensions.toAnalyticsRoomSize +import im.vector.app.features.analytics.extensions.toAnalyticsJoinedRoom import im.vector.app.features.analytics.plan.JoinedRoom import im.vector.app.features.roomdirectory.JoinState import kotlinx.coroutines.Dispatchers @@ -37,6 +37,7 @@ import kotlinx.coroutines.launch import org.matrix.android.sdk.api.extensions.tryOrNull import org.matrix.android.sdk.api.query.QueryStringValue import org.matrix.android.sdk.api.session.Session +import org.matrix.android.sdk.api.session.getRoomSummary import org.matrix.android.sdk.api.session.identity.SharedState import org.matrix.android.sdk.api.session.identity.ThreePid import org.matrix.android.sdk.api.session.room.members.ChangeMembershipState @@ -72,6 +73,7 @@ class RoomPreviewViewModel @AssistedInject constructor( // we might want to check if the mail is bound to this account? // if it is the invite val threePids = session + .profileService() .getThreePids() .filterIsInstance() @@ -107,7 +109,7 @@ class RoomPreviewViewModel @AssistedInject constructor( } viewModelScope.launch(Dispatchers.IO) { val peekResult = tryOrNull { - session.peekRoom(initialState.roomAlias ?: initialState.roomId) + session.roomService().peekRoom(initialState.roomAlias ?: initialState.roomId) } when (peekResult) { @@ -226,7 +228,7 @@ class RoomPreviewViewModel @AssistedInject constructor( state.fromEmailInvite?.privateKey ?: "" ) - session.joinRoom(state.roomId, reason = null, thirdPartySigned) + session.roomService().joinRoom(state.roomId, reason = null, thirdPartySigned) } catch (failure: Throwable) { setState { copy( @@ -246,18 +248,14 @@ class RoomPreviewViewModel @AssistedInject constructor( } viewModelScope.launch { try { - session.joinRoom(state.roomId, viaServers = state.homeServers) - analyticsTracker.capture(JoinedRoom( - // Always false in this case (?) - isDM = false, - isSpace = false, - roomSize = state.numJoinMembers.toAnalyticsRoomSize(), - )) + session.roomService().joinRoom(state.roomId, viaServers = state.homeServers) // We do not update the joiningRoomsIds here, because, the room is not joined yet regarding the sync data. // Instead, we wait for the room to be joined } catch (failure: Throwable) { setState { copy(lastError = failure) } } + session.getRoomSummary(state.roomId) + ?.let { analyticsTracker.capture(it.toAnalyticsJoinedRoom(JoinedRoom.Trigger.RoomPreview)) } } } } diff --git a/vector/src/main/java/im/vector/app/features/roommemberprofile/RoomMemberProfileController.kt b/vector/src/main/java/im/vector/app/features/roommemberprofile/RoomMemberProfileController.kt index 545e9f7190..adc720b0a5 100644 --- a/vector/src/main/java/im/vector/app/features/roommemberprofile/RoomMemberProfileController.kt +++ b/vector/src/main/java/im/vector/app/features/roommemberprofile/RoomMemberProfileController.kt @@ -182,6 +182,15 @@ class RoomMemberProfileController @Inject constructor( // More buildProfileSection(stringProvider.getString(R.string.room_profile_section_more)) + if (!state.isMine) { + buildProfileAction( + id = "direct", + editable = false, + title = stringProvider.getString(R.string.room_member_open_or_create_dm), + action = { callback?.onOpenDmClicked() } + ) + } + buildProfileAction( id = "overrideColor", editable = false, @@ -194,13 +203,6 @@ class RoomMemberProfileController @Inject constructor( if (!state.isMine) { val membership = state.asyncMembership() ?: return - buildProfileAction( - id = "direct", - editable = false, - title = stringProvider.getString(R.string.room_member_open_or_create_dm), - action = { callback?.onOpenDmClicked() } - ) - if (!state.isSpace && state.hasReadReceipt) { buildProfileAction( id = "read_receipt", diff --git a/vector/src/main/java/im/vector/app/features/roommemberprofile/RoomMemberProfileFragment.kt b/vector/src/main/java/im/vector/app/features/roommemberprofile/RoomMemberProfileFragment.kt index 5fee420b21..032ab74153 100644 --- a/vector/src/main/java/im/vector/app/features/roommemberprofile/RoomMemberProfileFragment.kt +++ b/vector/src/main/java/im/vector/app/features/roommemberprofile/RoomMemberProfileFragment.kt @@ -47,8 +47,6 @@ import im.vector.app.databinding.DialogBaseEditTextBinding import im.vector.app.databinding.DialogShareQrCodeBinding import im.vector.app.databinding.FragmentMatrixProfileBinding import im.vector.app.databinding.ViewStubRoomMemberProfileHeaderBinding -import im.vector.app.features.MainActivity -import im.vector.app.features.MainActivityArgs import im.vector.app.features.analytics.plan.MobileScreen import im.vector.app.features.crypto.verification.VerificationBottomSheet import im.vector.app.features.displayname.getBestName @@ -113,7 +111,8 @@ class RoomMemberProfileFragment @Inject constructor( headerViews.memberProfileStateView.contentView = headerViews.memberProfileInfoContainer views.matrixProfileRecyclerView.configureWith(roomMemberProfileController, hasFixedSize = true, disableItemAnimation = true) roomMemberProfileController.callback = this - appBarStateChangeListener = MatrixItemAppBarStateChangeListener(headerView, + appBarStateChangeListener = MatrixItemAppBarStateChangeListener( + headerView, listOf( views.matrixProfileToolbarAvatarImageView, views.matrixProfileToolbarTitleView, @@ -133,20 +132,13 @@ class RoomMemberProfileFragment @Inject constructor( is RoomMemberProfileViewEvents.OnKickActionSuccess -> Unit is RoomMemberProfileViewEvents.OnSetPowerLevelSuccess -> Unit is RoomMemberProfileViewEvents.OnBanActionSuccess -> Unit - is RoomMemberProfileViewEvents.OnIgnoreActionSuccess -> handleOnIgnoreActionSuccess(it) + is RoomMemberProfileViewEvents.OnIgnoreActionSuccess -> Unit is RoomMemberProfileViewEvents.OnInviteActionSuccess -> Unit } } setupLongClicks() } - private fun handleOnIgnoreActionSuccess(action: RoomMemberProfileViewEvents.OnIgnoreActionSuccess) { - if (action.shouldPerformInitialSync) { - // A user has been un-ignored, perform a initial sync - MainActivity.restartApp(requireActivity(), MainActivityArgs(clearCache = true)) - } - } - private fun setupLongClicks() { headerViews.memberProfileNameView.copyOnLongClick() headerViews.memberProfileIdView.copyOnLongClick() diff --git a/vector/src/main/java/im/vector/app/features/roommemberprofile/RoomMemberProfileViewEvents.kt b/vector/src/main/java/im/vector/app/features/roommemberprofile/RoomMemberProfileViewEvents.kt index 1857f5cdc4..46983b52a4 100644 --- a/vector/src/main/java/im/vector/app/features/roommemberprofile/RoomMemberProfileViewEvents.kt +++ b/vector/src/main/java/im/vector/app/features/roommemberprofile/RoomMemberProfileViewEvents.kt @@ -19,13 +19,13 @@ package im.vector.app.features.roommemberprofile import im.vector.app.core.platform.VectorViewEvents /** - * Transient events for RoomMemberProfile + * Transient events for RoomMemberProfile. */ sealed class RoomMemberProfileViewEvents : VectorViewEvents { data class Loading(val message: CharSequence? = null) : RoomMemberProfileViewEvents() data class Failure(val throwable: Throwable) : RoomMemberProfileViewEvents() - data class OnIgnoreActionSuccess(val shouldPerformInitialSync: Boolean) : RoomMemberProfileViewEvents() + object OnIgnoreActionSuccess : RoomMemberProfileViewEvents() object OnSetPowerLevelSuccess : RoomMemberProfileViewEvents() object OnInviteActionSuccess : RoomMemberProfileViewEvents() object OnKickActionSuccess : RoomMemberProfileViewEvents() diff --git a/vector/src/main/java/im/vector/app/features/roommemberprofile/RoomMemberProfileViewModel.kt b/vector/src/main/java/im/vector/app/features/roommemberprofile/RoomMemberProfileViewModel.kt index addc2c71ef..fb1e222080 100644 --- a/vector/src/main/java/im/vector/app/features/roommemberprofile/RoomMemberProfileViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/roommemberprofile/RoomMemberProfileViewModel.kt @@ -48,6 +48,7 @@ import org.matrix.android.sdk.api.session.accountdata.UserAccountDataTypes import org.matrix.android.sdk.api.session.events.model.EventType import org.matrix.android.sdk.api.session.events.model.toContent import org.matrix.android.sdk.api.session.events.model.toModel +import org.matrix.android.sdk.api.session.getRoom import org.matrix.android.sdk.api.session.room.Room import org.matrix.android.sdk.api.session.room.members.roomMemberQueryParams import org.matrix.android.sdk.api.session.room.model.Membership @@ -86,8 +87,8 @@ class RoomMemberProfileViewModel @AssistedInject constructor( setState { copy( isMine = session.myUserId == this.userId, - userMatrixItem = room?.getRoomMember(initialState.userId)?.toMatrixItem()?.let { Success(it) } ?: Uninitialized, - hasReadReceipt = room?.getUserReadReceipt(initialState.userId) != null, + userMatrixItem = room?.membershipService()?.getRoomMember(initialState.userId)?.toMatrixItem()?.let { Success(it) } ?: Uninitialized, + hasReadReceipt = room?.readService()?.getUserReadReceipt(initialState.userId) != null, isSpace = room?.roomSummary()?.roomType == RoomType.SPACE ) } @@ -96,7 +97,7 @@ class RoomMemberProfileViewModel @AssistedInject constructor( viewModelScope.launch(Dispatchers.Main) { // Do we have a room member for this id. val roomMember = withContext(Dispatchers.Default) { - room?.getRoomMember(initialState.userId) + room?.membershipService()?.getRoomMember(initialState.userId) } // If not, we look for profile info on the server if (room == null || roomMember == null) { @@ -227,7 +228,7 @@ class RoomMemberProfileViewModel @AssistedInject constructor( viewModelScope.launch { _viewEvents.post(RoomMemberProfileViewEvents.Loading()) try { - room.sendStateEvent(EventType.STATE_ROOM_POWER_LEVELS, stateKey = "", newPowerLevelsContent) + room.stateService().sendStateEvent(EventType.STATE_ROOM_POWER_LEVELS, stateKey = "", newPowerLevelsContent) _viewEvents.post(RoomMemberProfileViewEvents.OnSetPowerLevelSuccess) } catch (failure: Throwable) { _viewEvents.post(RoomMemberProfileViewEvents.Failure(failure)) @@ -241,10 +242,12 @@ class RoomMemberProfileViewModel @AssistedInject constructor( if (state.isRoomEncrypted) { if (!state.isMine && state.userMXCrossSigningInfo?.isTrusted() == false) { // ok, let's find or create the DM room - _viewEvents.post(RoomMemberProfileViewEvents.StartVerification( - userId = state.userId, - canCrossSign = session.cryptoService().crossSigningService().canCrossSign() - )) + _viewEvents.post( + RoomMemberProfileViewEvents.StartVerification( + userId = state.userId, + canCrossSign = session.cryptoService().crossSigningService().canCrossSign() + ) + ) } } } @@ -256,7 +259,7 @@ class RoomMemberProfileViewModel @AssistedInject constructor( viewModelScope.launch { try { _viewEvents.post(RoomMemberProfileViewEvents.Loading()) - room.invite(initialState.userId) + room.membershipService().invite(initialState.userId) _viewEvents.post(RoomMemberProfileViewEvents.OnInviteActionSuccess) } catch (failure: Throwable) { _viewEvents.post(RoomMemberProfileViewEvents.Failure(failure)) @@ -271,7 +274,7 @@ class RoomMemberProfileViewModel @AssistedInject constructor( viewModelScope.launch { try { _viewEvents.post(RoomMemberProfileViewEvents.Loading()) - room.remove(initialState.userId, action.reason) + room.membershipService().remove(initialState.userId, action.reason) _viewEvents.post(RoomMemberProfileViewEvents.OnKickActionSuccess) } catch (failure: Throwable) { _viewEvents.post(RoomMemberProfileViewEvents.Failure(failure)) @@ -288,9 +291,9 @@ class RoomMemberProfileViewModel @AssistedInject constructor( try { _viewEvents.post(RoomMemberProfileViewEvents.Loading()) if (membership == Membership.BAN) { - room.unban(initialState.userId, action.reason) + room.membershipService().unban(initialState.userId, action.reason) } else { - room.ban(initialState.userId, action.reason) + room.membershipService().ban(initialState.userId, action.reason) } _viewEvents.post(RoomMemberProfileViewEvents.OnBanActionSuccess) } catch (failure: Throwable) { @@ -327,7 +330,8 @@ class RoomMemberProfileViewModel @AssistedInject constructor( private suspend fun fetchProfileInfo() { val result = runCatchingToAsync { - session.getProfile(initialState.userId) + session.profileService() + .getProfile(initialState.userId) .let { User.fromJson(initialState.userId, it) } .toMatrixItem() } @@ -386,11 +390,11 @@ class RoomMemberProfileViewModel @AssistedInject constructor( viewModelScope.launch { val event = try { if (isIgnored) { - session.unIgnoreUserIds(listOf(state.userId)) + session.userService().unIgnoreUserIds(listOf(state.userId)) } else { - session.ignoreUserIds(listOf(state.userId)) + session.userService().ignoreUserIds(listOf(state.userId)) } - RoomMemberProfileViewEvents.OnIgnoreActionSuccess(isIgnored) + RoomMemberProfileViewEvents.OnIgnoreActionSuccess } catch (failure: Throwable) { RoomMemberProfileViewEvents.Failure(failure) } diff --git a/vector/src/main/java/im/vector/app/features/roommemberprofile/devices/DeviceListBottomSheet.kt b/vector/src/main/java/im/vector/app/features/roommemberprofile/devices/DeviceListBottomSheet.kt index 8df0b3ffd5..35f431db1d 100644 --- a/vector/src/main/java/im/vector/app/features/roommemberprofile/devices/DeviceListBottomSheet.kt +++ b/vector/src/main/java/im/vector/app/features/roommemberprofile/devices/DeviceListBottomSheet.kt @@ -96,7 +96,8 @@ class DeviceListBottomSheet : private fun showFragment(fragmentClass: KClass, bundle: Bundle) { if (childFragmentManager.findFragmentByTag(fragmentClass.simpleName) == null) { childFragmentManager.commitTransaction { - replace(R.id.bottomSheetFragmentContainer, + replace( + R.id.bottomSheetFragmentContainer, fragmentClass.java, bundle, fragmentClass.simpleName diff --git a/vector/src/main/java/im/vector/app/features/roommemberprofile/devices/DeviceListBottomSheetViewEvents.kt b/vector/src/main/java/im/vector/app/features/roommemberprofile/devices/DeviceListBottomSheetViewEvents.kt index 7a2990775d..8c6cba6cd9 100644 --- a/vector/src/main/java/im/vector/app/features/roommemberprofile/devices/DeviceListBottomSheetViewEvents.kt +++ b/vector/src/main/java/im/vector/app/features/roommemberprofile/devices/DeviceListBottomSheetViewEvents.kt @@ -19,7 +19,7 @@ package im.vector.app.features.roommemberprofile.devices import im.vector.app.core.platform.VectorViewEvents /** - * Transient events for device list screen + * Transient events for device list screen. */ sealed class DeviceListBottomSheetViewEvents : VectorViewEvents { data class Verify(val userId: String, val txID: String) : DeviceListBottomSheetViewEvents() diff --git a/vector/src/main/java/im/vector/app/features/roommemberprofile/devices/DeviceListBottomSheetViewModel.kt b/vector/src/main/java/im/vector/app/features/roommemberprofile/devices/DeviceListBottomSheetViewModel.kt index 5c64232a5a..19040a1fde 100644 --- a/vector/src/main/java/im/vector/app/features/roommemberprofile/devices/DeviceListBottomSheetViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/roommemberprofile/devices/DeviceListBottomSheetViewModel.kt @@ -33,6 +33,7 @@ import org.matrix.android.sdk.api.session.Session import org.matrix.android.sdk.api.session.crypto.crosssigning.MXCrossSigningInfo import org.matrix.android.sdk.api.session.crypto.model.CryptoDeviceInfo import org.matrix.android.sdk.api.session.crypto.verification.VerificationMethod +import org.matrix.android.sdk.api.session.getUser import org.matrix.android.sdk.api.util.MatrixItem import org.matrix.android.sdk.api.util.toMatrixItem import org.matrix.android.sdk.flow.flow diff --git a/vector/src/main/java/im/vector/app/features/roommemberprofile/devices/DeviceTrustInfoEpoxyController.kt b/vector/src/main/java/im/vector/app/features/roommemberprofile/devices/DeviceTrustInfoEpoxyController.kt index a52f56870a..0e25ec5f8f 100644 --- a/vector/src/main/java/im/vector/app/features/roommemberprofile/devices/DeviceTrustInfoEpoxyController.kt +++ b/vector/src/main/java/im/vector/app/features/roommemberprofile/devices/DeviceTrustInfoEpoxyController.kt @@ -36,7 +36,7 @@ class DeviceTrustInfoEpoxyController @Inject constructor(private val stringProvi private val colorProvider: ColorProvider, private val dimensionConverter: DimensionConverter, private val vectorPreferences: VectorPreferences) : - TypedEpoxyController() { + TypedEpoxyController() { interface InteractionListener { fun onVerifyManually(device: CryptoDeviceInfo) @@ -65,14 +65,22 @@ class DeviceTrustInfoEpoxyController @Inject constructor(private val stringProvi apply { if (isVerified) { // TODO FORMAT - text(host.stringProvider.getString(R.string.verification_profile_device_verified_because, - data.userItem?.displayName ?: "", - data.userItem?.id ?: "").toEpoxyCharSequence()) + text( + host.stringProvider.getString( + R.string.verification_profile_device_verified_because, + data.userItem?.displayName ?: "", + data.userItem?.id ?: "" + ).toEpoxyCharSequence() + ) } else { // TODO what if mine - text(host.stringProvider.getString(R.string.verification_profile_device_new_signing, - data.userItem?.displayName ?: "", - data.userItem?.id ?: "").toEpoxyCharSequence()) + text( + host.stringProvider.getString( + R.string.verification_profile_device_new_signing, + data.userItem?.displayName ?: "", + data.userItem?.id ?: "" + ).toEpoxyCharSequence() + ) } } // text(stringProvider.getString(R.string.verification_profile_device_untrust_info)) diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/RoomProfileActivity.kt b/vector/src/main/java/im/vector/app/features/roomprofile/RoomProfileActivity.kt index 12a5d94eca..29a2f73c78 100644 --- a/vector/src/main/java/im/vector/app/features/roomprofile/RoomProfileActivity.kt +++ b/vector/src/main/java/im/vector/app/features/roomprofile/RoomProfileActivity.kt @@ -84,21 +84,21 @@ class RoomProfileActivity : addFragment(views.simpleFragmentContainer, RoomProfileFragment::class.java, roomProfileArgs) addFragmentToBackstack(views.simpleFragmentContainer, RoomSettingsFragment::class.java, roomProfileArgs) } - EXTRA_DIRECT_ACCESS_ROOM_MEMBERS -> { + EXTRA_DIRECT_ACCESS_ROOM_MEMBERS -> { addFragment(views.simpleFragmentContainer, RoomMemberListFragment::class.java, roomProfileArgs) } - else -> addFragment(views.simpleFragmentContainer, RoomProfileFragment::class.java, roomProfileArgs) + else -> addFragment(views.simpleFragmentContainer, RoomProfileFragment::class.java, roomProfileArgs) } } sharedActionViewModel .stream() .onEach { sharedAction -> when (sharedAction) { - RoomProfileSharedAction.OpenRoomMembers -> openRoomMembers() - RoomProfileSharedAction.OpenRoomSettings -> openRoomSettings() - RoomProfileSharedAction.OpenRoomAliasesSettings -> openRoomAlias() - RoomProfileSharedAction.OpenRoomPermissionsSettings -> openRoomPermissions() - RoomProfileSharedAction.OpenRoomUploads -> openRoomUploads() + RoomProfileSharedAction.OpenRoomMembers -> openRoomMembers() + RoomProfileSharedAction.OpenRoomSettings -> openRoomSettings() + RoomProfileSharedAction.OpenRoomAliasesSettings -> openRoomAlias() + RoomProfileSharedAction.OpenRoomPermissionsSettings -> openRoomPermissions() + RoomProfileSharedAction.OpenRoomUploads -> openRoomUploads() RoomProfileSharedAction.OpenBannedRoomMembers -> openBannedRoomMembers() RoomProfileSharedAction.OpenRoomNotificationSettings -> openRoomNotificationSettings() } diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/RoomProfileController.kt b/vector/src/main/java/im/vector/app/features/roomprofile/RoomProfileController.kt index 372b4e5a70..06f56bff89 100644 --- a/vector/src/main/java/im/vector/app/features/roomprofile/RoomProfileController.kt +++ b/vector/src/main/java/im/vector/app/features/roomprofile/RoomProfileController.kt @@ -179,11 +179,13 @@ class RoomProfileController @Inject constructor( buildProfileSection(stringProvider.getString(R.string.room_profile_section_more)) buildProfileAction( id = "settings", - title = stringProvider.getString(if (roomSummary.isDirect) { - R.string.direct_room_profile_section_more_settings - } else { - R.string.room_profile_section_more_settings - }), + title = stringProvider.getString( + if (roomSummary.isDirect) { + R.string.direct_room_profile_section_more_settings + } else { + R.string.room_profile_section_more_settings + } + ), icon = R.drawable.ic_room_profile_settings, action = { callback?.onSettingsClicked() } ) @@ -228,11 +230,13 @@ class RoomProfileController @Inject constructor( } buildProfileAction( id = "leave", - title = stringProvider.getString(if (roomSummary.isDirect) { - R.string.direct_room_profile_section_more_leave - } else { - R.string.room_profile_section_more_leave - }), + title = stringProvider.getString( + if (roomSummary.isDirect) { + R.string.direct_room_profile_section_more_leave + } else { + R.string.room_profile_section_more_leave + } + ), divider = false, destructive = true, icon = R.drawable.ic_room_actions_leave, diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/RoomProfileFragment.kt b/vector/src/main/java/im/vector/app/features/roomprofile/RoomProfileFragment.kt index 3944066584..7be92bfdcf 100644 --- a/vector/src/main/java/im/vector/app/features/roomprofile/RoomProfileFragment.kt +++ b/vector/src/main/java/im/vector/app/features/roomprofile/RoomProfileFragment.kt @@ -113,9 +113,11 @@ class RoomProfileFragment @Inject constructor( setupRecyclerView() appBarStateChangeListener = MatrixItemAppBarStateChangeListener( headerView, - listOf(views.matrixProfileToolbarAvatarImageView, + listOf( + views.matrixProfileToolbarAvatarImageView, views.matrixProfileToolbarTitleView, - views.matrixProfileDecorationToolbarAvatarImageView) + views.matrixProfileDecorationToolbarAvatarImageView + ) ) views.matrixProfileAppBarLayout.addOnOffsetChangedListener(appBarStateChangeListener) roomProfileViewModel.observeViewEvents { @@ -269,11 +271,13 @@ class RoomProfileFragment @Inject constructor( override fun createShortcut() { // Ask the view model to prepare it... roomProfileViewModel.handle(RoomProfileAction.CreateShortcut) - analyticsTracker.capture(Interaction( - index = null, - interactionType = null, - name = Interaction.Name.MobileRoomAddHome - )) + analyticsTracker.capture( + Interaction( + index = null, + interactionType = null, + name = Interaction.Name.MobileRoomAddHome + ) + ) } private fun addShortcut(onShortcutReady: RoomProfileViewEvents.OnShortcutReady) { diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/RoomProfileSharedAction.kt b/vector/src/main/java/im/vector/app/features/roomprofile/RoomProfileSharedAction.kt index eb4ab56634..7d62bb86a1 100644 --- a/vector/src/main/java/im/vector/app/features/roomprofile/RoomProfileSharedAction.kt +++ b/vector/src/main/java/im/vector/app/features/roomprofile/RoomProfileSharedAction.kt @@ -19,7 +19,7 @@ package im.vector.app.features.roomprofile import im.vector.app.core.platform.VectorSharedAction /** - * Supported navigation actions for [RoomProfileActivity] + * Supported navigation actions for [RoomProfileActivity]. */ sealed class RoomProfileSharedAction : VectorSharedAction { object OpenRoomSettings : RoomProfileSharedAction() diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/RoomProfileViewEvents.kt b/vector/src/main/java/im/vector/app/features/roomprofile/RoomProfileViewEvents.kt index 181115091c..f8ffed4af6 100644 --- a/vector/src/main/java/im/vector/app/features/roomprofile/RoomProfileViewEvents.kt +++ b/vector/src/main/java/im/vector/app/features/roomprofile/RoomProfileViewEvents.kt @@ -20,7 +20,7 @@ import androidx.core.content.pm.ShortcutInfoCompat import im.vector.app.core.platform.VectorViewEvents /** - * Transient events for RoomProfile + * Transient events for RoomProfile. */ sealed class RoomProfileViewEvents : VectorViewEvents { data class Loading(val message: CharSequence? = null) : RoomProfileViewEvents() diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/RoomProfileViewModel.kt b/vector/src/main/java/im/vector/app/features/roomprofile/RoomProfileViewModel.kt index 7c6623b087..1428dc0cf0 100644 --- a/vector/src/main/java/im/vector/app/features/roomprofile/RoomProfileViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/roomprofile/RoomProfileViewModel.kt @@ -39,6 +39,8 @@ import org.matrix.android.sdk.api.query.QueryStringValue import org.matrix.android.sdk.api.session.Session import org.matrix.android.sdk.api.session.events.model.EventType import org.matrix.android.sdk.api.session.events.model.toModel +import org.matrix.android.sdk.api.session.getRoom +import org.matrix.android.sdk.api.session.room.getStateEvent import org.matrix.android.sdk.api.session.room.members.roomMemberQueryParams import org.matrix.android.sdk.api.session.room.model.Membership import org.matrix.android.sdk.api.session.room.model.create.RoomCreateContent @@ -96,9 +98,9 @@ class RoomProfileViewModel @AssistedInject constructor( copy( roomCreateContent = async, // This is a shortcut, we should do the next lines elsewhere, but keep it like that for the moment. - recommendedRoomVersion = room.getRecommendedVersion(), - isUsingUnstableRoomVersion = room.isUsingUnstableRoomVersion(), - canUpgradeRoom = room.userMayUpgradeRoom(session.myUserId), + recommendedRoomVersion = room.roomVersionService().getRecommendedVersion(), + isUsingUnstableRoomVersion = room.roomVersionService().isUsingUnstableRoomVersion(), + canUpgradeRoom = room.roomVersionService().userMayUpgradeRoom(session.myUserId), isTombstoned = room.getStateEvent(EventType.STATE_ROOM_TOMBSTONE) != null ) } @@ -143,14 +145,14 @@ class RoomProfileViewModel @AssistedInject constructor( } fun isPublicRoom(): Boolean { - return room.isPublic() + return room.stateService().isPublic() } private fun handleEnableEncryption() { postLoading(true) viewModelScope.launch { - val result = runCatching { room.enableEncryption() } + val result = runCatching { room.roomCryptoService().enableEncryption() } postLoading(false) result.onFailure { failure -> _viewEvents.post(RoomProfileViewEvents.Failure(failure)) @@ -177,7 +179,7 @@ class RoomProfileViewModel @AssistedInject constructor( private fun handleChangeNotificationMode(action: RoomProfileAction.ChangeRoomNotificationState) { viewModelScope.launch { try { - room.setRoomNotificationState(action.notificationState) + room.roomPushRuleService().setRoomNotificationState(action.notificationState) } catch (failure: Throwable) { _viewEvents.post(RoomProfileViewEvents.Failure(failure)) } @@ -188,12 +190,14 @@ class RoomProfileViewModel @AssistedInject constructor( _viewEvents.post(RoomProfileViewEvents.Loading(stringProvider.getString(R.string.room_profile_leaving_room))) viewModelScope.launch { try { - session.leaveRoom(room.roomId) - analyticsTracker.capture(Interaction( - index = null, - interactionType = null, - name = Interaction.Name.MobileRoomLeave - )) + session.roomService().leaveRoom(room.roomId) + analyticsTracker.capture( + Interaction( + index = null, + interactionType = null, + name = Interaction.Name.MobileRoomLeave + ) + ) // Do nothing, we will be closing the room automatically when it will get back from sync } catch (failure: Throwable) { _viewEvents.post(RoomProfileViewEvents.Failure(failure)) @@ -212,7 +216,7 @@ class RoomProfileViewModel @AssistedInject constructor( _viewEvents.post(RoomProfileViewEvents.Loading()) session.coroutineScope.launch { try { - room.enableEncryption(force = true) + room.roomCryptoService().enableEncryption(force = true) } catch (failure: Throwable) { Timber.e(failure, "Failed to restore encryption state in room ${room.roomId}") _viewEvents.post(RoomProfileViewEvents.Failure(failure)) diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/alias/RoomAliasController.kt b/vector/src/main/java/im/vector/app/features/roomprofile/alias/RoomAliasController.kt index fcf6bc3a47..3db1e384bc 100644 --- a/vector/src/main/java/im/vector/app/features/roomprofile/alias/RoomAliasController.kt +++ b/vector/src/main/java/im/vector/app/features/roomprofile/alias/RoomAliasController.kt @@ -98,8 +98,12 @@ class RoomAliasController @Inject constructor( is Fail -> { errorWithRetryItem { id("rd_error") - text(host.stringProvider.getString(R.string.room_alias_publish_to_directory_error, - host.errorFormatter.toHumanReadable(data.roomDirectoryVisibility.error))) + text( + host.stringProvider.getString( + R.string.room_alias_publish_to_directory_error, + host.errorFormatter.toHumanReadable(data.roomDirectoryVisibility.error) + ) + ) listener { host.callback?.retry() } } } diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/alias/RoomAliasViewEvents.kt b/vector/src/main/java/im/vector/app/features/roomprofile/alias/RoomAliasViewEvents.kt index bbd44741b5..725755a16b 100644 --- a/vector/src/main/java/im/vector/app/features/roomprofile/alias/RoomAliasViewEvents.kt +++ b/vector/src/main/java/im/vector/app/features/roomprofile/alias/RoomAliasViewEvents.kt @@ -20,7 +20,7 @@ package im.vector.app.features.roomprofile.alias import im.vector.app.core.platform.VectorViewEvents /** - * Transient events for room settings screen + * Transient events for room settings screen. */ sealed class RoomAliasViewEvents : VectorViewEvents { data class Failure(val throwable: Throwable) : RoomAliasViewEvents() diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/alias/RoomAliasViewModel.kt b/vector/src/main/java/im/vector/app/features/roomprofile/alias/RoomAliasViewModel.kt index adffbcbd06..2641eb4184 100644 --- a/vector/src/main/java/im/vector/app/features/roomprofile/alias/RoomAliasViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/roomprofile/alias/RoomAliasViewModel.kt @@ -36,6 +36,7 @@ import org.matrix.android.sdk.api.query.QueryStringValue import org.matrix.android.sdk.api.session.Session import org.matrix.android.sdk.api.session.events.model.EventType import org.matrix.android.sdk.api.session.events.model.toModel +import org.matrix.android.sdk.api.session.getRoom import org.matrix.android.sdk.api.session.room.model.RoomCanonicalAliasContent import org.matrix.android.sdk.api.session.room.powerlevels.PowerLevelsHelper import org.matrix.android.sdk.flow.flow @@ -72,7 +73,7 @@ class RoomAliasViewModel @AssistedInject constructor(@Assisted initialState: Roo } viewModelScope.launch { runCatching { - session.getRoomDirectoryVisibility(room.roomId) + session.roomDirectoryService().getRoomDirectoryVisibility(room.roomId) }.fold( { setState { @@ -108,7 +109,7 @@ class RoomAliasViewModel @AssistedInject constructor(@Assisted initialState: Roo } viewModelScope.launch { - runCatching { room.getRoomAliases() } + runCatching { room.aliasService().getRoomAliases() } .fold( { setState { copy(localAliases = Success(it.sorted())) } @@ -205,7 +206,7 @@ class RoomAliasViewModel @AssistedInject constructor(@Assisted initialState: Roo postLoading(true) viewModelScope.launch { runCatching { - session.setRoomDirectoryVisibility(room.roomId, action.roomDirectoryVisibility) + session.roomDirectoryService().setRoomDirectoryVisibility(room.roomId, action.roomDirectoryVisibility) }.fold( { setState { @@ -303,7 +304,7 @@ class RoomAliasViewModel @AssistedInject constructor(@Assisted initialState: Roo postLoading(true) viewModelScope.launch { try { - room.updateCanonicalAlias(canonicalAlias, alternativeAliases) + room.stateService().updateCanonicalAlias(canonicalAlias, alternativeAliases) setState { copy( isLoading = false, @@ -327,7 +328,7 @@ class RoomAliasViewModel @AssistedInject constructor(@Assisted initialState: Roo ) } viewModelScope.launch { - runCatching { room.addAlias(previousState.value) } + runCatching { room.aliasService().addAlias(previousState.value) } .onFailure { setState { copy( @@ -354,7 +355,7 @@ class RoomAliasViewModel @AssistedInject constructor(@Assisted initialState: Roo private fun handleRemoveLocalAlias(action: RoomAliasAction.RemoveLocalAlias) { postLoading(true) viewModelScope.launch { - runCatching { session.deleteRoomAlias(action.alias) } + runCatching { session.roomService().deleteRoomAlias(action.alias) } .onFailure { setState { copy(isLoading = false) diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/alias/detail/RoomAliasBottomSheet.kt b/vector/src/main/java/im/vector/app/features/roomprofile/alias/detail/RoomAliasBottomSheet.kt index 56dbcbfba4..6d6d4564da 100644 --- a/vector/src/main/java/im/vector/app/features/roomprofile/alias/detail/RoomAliasBottomSheet.kt +++ b/vector/src/main/java/im/vector/app/features/roomprofile/alias/detail/RoomAliasBottomSheet.kt @@ -42,7 +42,7 @@ data class RoomAliasBottomSheetArgs( ) : Parcelable /** - * Bottom sheet fragment that shows room alias information with list of contextual actions + * Bottom sheet fragment that shows room alias information with list of contextual actions. */ @AndroidEntryPoint class RoomAliasBottomSheet : @@ -92,13 +92,15 @@ class RoomAliasBottomSheet : isLocal: Boolean, canEditCanonicalAlias: Boolean): RoomAliasBottomSheet { return RoomAliasBottomSheet().apply { - setArguments(RoomAliasBottomSheetArgs( - alias = alias, - isPublished = isPublished, - isMainAlias = isMainAlias, - isLocal = isLocal, - canEditCanonicalAlias = canEditCanonicalAlias - )) + setArguments( + RoomAliasBottomSheetArgs( + alias = alias, + isPublished = isPublished, + isMainAlias = isMainAlias, + isLocal = isLocal, + canEditCanonicalAlias = canEditCanonicalAlias + ) + ) } } } diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/alias/detail/RoomAliasBottomSheetController.kt b/vector/src/main/java/im/vector/app/features/roomprofile/alias/detail/RoomAliasBottomSheetController.kt index bfe6847fdf..13431d1ef7 100644 --- a/vector/src/main/java/im/vector/app/features/roomprofile/alias/detail/RoomAliasBottomSheetController.kt +++ b/vector/src/main/java/im/vector/app/features/roomprofile/alias/detail/RoomAliasBottomSheetController.kt @@ -22,7 +22,7 @@ import im.vector.app.core.ui.bottomsheet.bottomSheetTitleItem import javax.inject.Inject /** - * Epoxy controller for room alias actions + * Epoxy controller for room alias actions. */ class RoomAliasBottomSheetController @Inject constructor() : TypedEpoxyController() { diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/alias/detail/RoomAliasBottomSheetSharedAction.kt b/vector/src/main/java/im/vector/app/features/roomprofile/alias/detail/RoomAliasBottomSheetSharedAction.kt index 7625972b05..d50c19635d 100644 --- a/vector/src/main/java/im/vector/app/features/roomprofile/alias/detail/RoomAliasBottomSheetSharedAction.kt +++ b/vector/src/main/java/im/vector/app/features/roomprofile/alias/detail/RoomAliasBottomSheetSharedAction.kt @@ -25,7 +25,7 @@ sealed class RoomAliasBottomSheetSharedAction( @StringRes val titleRes: Int, @DrawableRes val iconResId: Int = 0, val destructive: Boolean = false) : - VectorSharedAction { + VectorSharedAction { data class ShareAlias(val matrixTo: String) : RoomAliasBottomSheetSharedAction( R.string.action_share, diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/alias/detail/RoomAliasBottomSheetSharedActionViewModel.kt b/vector/src/main/java/im/vector/app/features/roomprofile/alias/detail/RoomAliasBottomSheetSharedActionViewModel.kt index 5f71783515..9371bd3d39 100644 --- a/vector/src/main/java/im/vector/app/features/roomprofile/alias/detail/RoomAliasBottomSheetSharedActionViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/roomprofile/alias/detail/RoomAliasBottomSheetSharedActionViewModel.kt @@ -20,6 +20,6 @@ import im.vector.app.core.platform.VectorSharedActionViewModel import javax.inject.Inject /** - * Activity shared view model to handle room alias quick actions + * Activity shared view model to handle room alias quick actions. */ class RoomAliasBottomSheetSharedActionViewModel @Inject constructor() : VectorSharedActionViewModel() diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/banned/RoomBannedMemberListViewModel.kt b/vector/src/main/java/im/vector/app/features/roomprofile/banned/RoomBannedMemberListViewModel.kt index ec249c75ba..1fef5e8ff2 100644 --- a/vector/src/main/java/im/vector/app/features/roomprofile/banned/RoomBannedMemberListViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/roomprofile/banned/RoomBannedMemberListViewModel.kt @@ -32,6 +32,8 @@ import org.matrix.android.sdk.api.query.QueryStringValue import org.matrix.android.sdk.api.session.Session import org.matrix.android.sdk.api.session.events.model.EventType import org.matrix.android.sdk.api.session.events.model.toModel +import org.matrix.android.sdk.api.session.getRoom +import org.matrix.android.sdk.api.session.room.getStateEvent import org.matrix.android.sdk.api.session.room.members.roomMemberQueryParams import org.matrix.android.sdk.api.session.room.model.Membership import org.matrix.android.sdk.api.session.room.model.RoomMemberContent @@ -114,7 +116,7 @@ class RoomBannedMemberListViewModel @AssistedInject constructor(@Assisted initia } viewModelScope.launch(Dispatchers.IO) { try { - room.unban(roomMemberSummary.userId, null) + room.membershipService().unban(roomMemberSummary.userId, null) } catch (failure: Throwable) { _viewEvents.post(RoomBannedMemberListViewEvents.ToastError(stringProvider.getString(R.string.failed_to_unban))) } finally { diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/members/RoomMemberListController.kt b/vector/src/main/java/im/vector/app/features/roomprofile/members/RoomMemberListController.kt index 07236fb397..ebcebfa470 100644 --- a/vector/src/main/java/im/vector/app/features/roomprofile/members/RoomMemberListController.kt +++ b/vector/src/main/java/im/vector/app/features/roomprofile/members/RoomMemberListController.kt @@ -133,6 +133,7 @@ class RoomMemberListController @Inject constructor( } showPresence(true) userPresence(roomMember.userPresence) + ignoredUser(roomMember.userId in data.ignoredUserIds) powerLevelLabel( span { span(powerLabel) { diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/members/RoomMemberListViewModel.kt b/vector/src/main/java/im/vector/app/features/roomprofile/members/RoomMemberListViewModel.kt index 1adf35a098..8863d18560 100644 --- a/vector/src/main/java/im/vector/app/features/roomprofile/members/RoomMemberListViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/roomprofile/members/RoomMemberListViewModel.kt @@ -39,6 +39,7 @@ import org.matrix.android.sdk.api.session.Session import org.matrix.android.sdk.api.session.crypto.model.RoomEncryptionTrustLevel import org.matrix.android.sdk.api.session.events.model.EventType import org.matrix.android.sdk.api.session.events.model.toModel +import org.matrix.android.sdk.api.session.getRoom import org.matrix.android.sdk.api.session.room.members.roomMemberQueryParams import org.matrix.android.sdk.api.session.room.model.Membership import org.matrix.android.sdk.api.session.room.model.PowerLevelsContent @@ -69,6 +70,7 @@ class RoomMemberListViewModel @AssistedInject constructor(@Assisted initialState observeThirdPartyInvites() observeRoomSummary() observePowerLevel() + observeIgnoredUsers() } private fun observeRoomMemberSummaries() { @@ -90,7 +92,7 @@ class RoomMemberListViewModel @AssistedInject constructor(@Assisted initialState copy(roomMemberSummaries = async) } - if (room.isEncrypted()) { + if (room.roomCryptoService().isEncrypted()) { room.flow().liveRoomMembers(roomMemberQueryParams) .flatMapLatest { membersSummary -> session.cryptoService().getLiveCryptoDeviceInfo(membersSummary.map { it.userId }) @@ -148,6 +150,16 @@ class RoomMemberListViewModel @AssistedInject constructor(@Assisted initialState } } + private fun observeIgnoredUsers() { + session.flow() + .liveIgnoredUsers() + .execute { async -> + copy( + ignoredUserIds = async.invoke().orEmpty().map { it.userId } + ) + } + } + private fun buildRoomMemberSummaries(powerLevelsContent: PowerLevelsContent, roomMembers: List): RoomMemberSummaries { val admins = ArrayList() val moderators = ArrayList() @@ -185,7 +197,7 @@ class RoomMemberListViewModel @AssistedInject constructor(@Assisted initialState private fun handleRevokeThreePidInvite(action: RoomMemberListAction.RevokeThreePidInvite) { viewModelScope.launch { - room.sendStateEvent( + room.stateService().sendStateEvent( eventType = EventType.STATE_ROOM_THIRD_PARTY_INVITE, stateKey = action.stateKey, body = emptyMap() diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/members/RoomMemberListViewState.kt b/vector/src/main/java/im/vector/app/features/roomprofile/members/RoomMemberListViewState.kt index 93ed7fa719..47a89b523a 100644 --- a/vector/src/main/java/im/vector/app/features/roomprofile/members/RoomMemberListViewState.kt +++ b/vector/src/main/java/im/vector/app/features/roomprofile/members/RoomMemberListViewState.kt @@ -32,6 +32,7 @@ data class RoomMemberListViewState( val roomId: String, val roomSummary: Async = Uninitialized, val roomMemberSummaries: Async = Uninitialized, + val ignoredUserIds: List = emptyList(), val filter: String = "", val threePidInvites: Async> = Uninitialized, val trustLevelMap: Async> = Uninitialized, diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/notifications/RoomNotificationSettingsController.kt b/vector/src/main/java/im/vector/app/features/roomprofile/notifications/RoomNotificationSettingsController.kt index 8f6e8f54c1..3ee0a00f28 100644 --- a/vector/src/main/java/im/vector/app/features/roomprofile/notifications/RoomNotificationSettingsController.kt +++ b/vector/src/main/java/im/vector/app/features/roomprofile/notifications/RoomNotificationSettingsController.kt @@ -42,7 +42,7 @@ class RoomNotificationSettingsController @Inject constructor() : TypedEpoxyContr id("roomNotificationSettingsHeader") textRes(R.string.room_settings_room_notifications_notify_me) } - data.notificationOptions.forEach { notificationState -> + data.notificationOptions.forEach { notificationState -> val title = titleForNotificationState(notificationState) radioButtonItem { id(notificationState.name) @@ -67,6 +67,6 @@ class RoomNotificationSettingsController @Inject constructor() : TypedEpoxyContr RoomNotificationState.ALL_MESSAGES_NOISY -> R.string.room_settings_all_messages RoomNotificationState.MENTIONS_ONLY -> R.string.room_settings_mention_and_keyword_only RoomNotificationState.MUTE -> R.string.room_settings_none - else -> null + else -> null } } diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/notifications/RoomNotificationSettingsViewModel.kt b/vector/src/main/java/im/vector/app/features/roomprofile/notifications/RoomNotificationSettingsViewModel.kt index 26db6b001e..be7e9173b1 100644 --- a/vector/src/main/java/im/vector/app/features/roomprofile/notifications/RoomNotificationSettingsViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/roomprofile/notifications/RoomNotificationSettingsViewModel.kt @@ -26,6 +26,7 @@ import im.vector.app.core.di.hiltMavericksViewModelFactory import im.vector.app.core.platform.VectorViewModel import kotlinx.coroutines.launch import org.matrix.android.sdk.api.session.Session +import org.matrix.android.sdk.api.session.getRoom import org.matrix.android.sdk.flow.flow import org.matrix.android.sdk.flow.unwrap @@ -36,7 +37,7 @@ class RoomNotificationSettingsViewModel @AssistedInject constructor( @AssistedFactory interface Factory : MavericksAssistedViewModelFactory { - override fun create(initialState: RoomNotificationSettingsViewState): RoomNotificationSettingsViewModel + override fun create(initialState: RoomNotificationSettingsViewState): RoomNotificationSettingsViewModel } companion object : MavericksViewModelFactory by hiltMavericksViewModelFactory() @@ -73,7 +74,7 @@ class RoomNotificationSettingsViewModel @AssistedInject constructor( private fun handleSelectNotificationState(action: RoomNotificationSettingsAction.SelectNotificationState) { setState { copy(isLoading = true) } viewModelScope.launch { - runCatching { room.setRoomNotificationState(action.notificationState) } + runCatching { room.roomPushRuleService().setRoomNotificationState(action.notificationState) } .fold( { setState { diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/notifications/RoomNotificationSettingsViewState.kt b/vector/src/main/java/im/vector/app/features/roomprofile/notifications/RoomNotificationSettingsViewState.kt index d6d1176c36..5c1e9e6bc5 100644 --- a/vector/src/main/java/im/vector/app/features/roomprofile/notifications/RoomNotificationSettingsViewState.kt +++ b/vector/src/main/java/im/vector/app/features/roomprofile/notifications/RoomNotificationSettingsViewState.kt @@ -30,7 +30,7 @@ data class RoomNotificationSettingsViewState( val roomSummary: Async = Uninitialized, val isLoading: Boolean = false, val notificationState: Async = Uninitialized -) : MavericksState { +) : MavericksState { constructor(args: RoomProfileArgs) : this(roomId = args.roomId) constructor(args: RoomListActionsArgs) : this(roomId = args.roomId) } diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/permissions/RoomPermissionsController.kt b/vector/src/main/java/im/vector/app/features/roomprofile/permissions/RoomPermissionsController.kt index 9590d1bbc3..fffae6159c 100644 --- a/vector/src/main/java/im/vector/app/features/roomprofile/permissions/RoomPermissionsController.kt +++ b/vector/src/main/java/im/vector/app/features/roomprofile/permissions/RoomPermissionsController.kt @@ -132,12 +132,15 @@ class RoomPermissionsController @Inject constructor( settingsInfoItem { id("notice") - helperText(host.stringProvider.getString( - if (editable) { - if (isSpace) R.string.space_permissions_notice else R.string.room_permissions_notice - } else { - if (isSpace) R.string.space_permissions_notice_read_only else R.string.room_permissions_notice_read_only - })) + helperText( + host.stringProvider.getString( + if (editable) { + if (isSpace) R.string.space_permissions_notice else R.string.room_permissions_notice + } else { + if (isSpace) R.string.space_permissions_notice_read_only else R.string.room_permissions_notice_read_only + } + ) + ) } // Useful permissions diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/permissions/RoomPermissionsViewEvents.kt b/vector/src/main/java/im/vector/app/features/roomprofile/permissions/RoomPermissionsViewEvents.kt index 8994398cf3..8994948e1d 100644 --- a/vector/src/main/java/im/vector/app/features/roomprofile/permissions/RoomPermissionsViewEvents.kt +++ b/vector/src/main/java/im/vector/app/features/roomprofile/permissions/RoomPermissionsViewEvents.kt @@ -20,7 +20,7 @@ package im.vector.app.features.roomprofile.permissions import im.vector.app.core.platform.VectorViewEvents /** - * Transient events for room settings screen + * Transient events for room settings screen. */ sealed class RoomPermissionsViewEvents : VectorViewEvents { data class Failure(val throwable: Throwable) : RoomPermissionsViewEvents() diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/permissions/RoomPermissionsViewModel.kt b/vector/src/main/java/im/vector/app/features/roomprofile/permissions/RoomPermissionsViewModel.kt index 6fbc545b6c..e387cca004 100644 --- a/vector/src/main/java/im/vector/app/features/roomprofile/permissions/RoomPermissionsViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/roomprofile/permissions/RoomPermissionsViewModel.kt @@ -31,6 +31,7 @@ import kotlinx.coroutines.launch import org.matrix.android.sdk.api.session.Session import org.matrix.android.sdk.api.session.events.model.EventType import org.matrix.android.sdk.api.session.events.model.toContent +import org.matrix.android.sdk.api.session.getRoom import org.matrix.android.sdk.api.session.room.model.PowerLevelsContent import org.matrix.android.sdk.api.session.room.powerlevels.PowerLevelsHelper import org.matrix.android.sdk.flow.flow @@ -38,7 +39,7 @@ import org.matrix.android.sdk.flow.unwrap class RoomPermissionsViewModel @AssistedInject constructor(@Assisted initialState: RoomPermissionsViewState, private val session: Session) : - VectorViewModel(initialState) { + VectorViewModel(initialState) { @AssistedFactory interface Factory : MavericksAssistedViewModelFactory { @@ -123,7 +124,7 @@ class RoomPermissionsViewModel @AssistedInject constructor(@Assisted initialStat } ) } - room.sendStateEvent(EventType.STATE_ROOM_POWER_LEVELS, stateKey = "", newPowerLevelsContent.toContent()) + room.stateService().sendStateEvent(EventType.STATE_ROOM_POWER_LEVELS, stateKey = "", newPowerLevelsContent.toContent()) setState { copy( isLoading = false diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/settings/RoomSettingsFragment.kt b/vector/src/main/java/im/vector/app/features/roomprofile/settings/RoomSettingsFragment.kt index eb69e36ba0..85151e1258 100644 --- a/vector/src/main/java/im/vector/app/features/roomprofile/settings/RoomSettingsFragment.kt +++ b/vector/src/main/java/im/vector/app/features/roomprofile/settings/RoomSettingsFragment.kt @@ -37,6 +37,7 @@ import im.vector.app.core.intent.getFilenameFromUri import im.vector.app.core.platform.OnBackPressed import im.vector.app.core.platform.VectorBaseFragment import im.vector.app.core.resources.ColorProvider +import im.vector.app.core.time.Clock import im.vector.app.core.utils.toast import im.vector.app.databinding.FragmentRoomSettingGenericBinding import im.vector.app.features.analytics.plan.MobileScreen @@ -57,7 +58,8 @@ import javax.inject.Inject class RoomSettingsFragment @Inject constructor( private val controller: RoomSettingsController, colorProvider: ColorProvider, - private val avatarRenderer: AvatarRenderer + private val avatarRenderer: AvatarRenderer, + clock: Clock, ) : VectorBaseFragment(), RoomSettingsController.Callback, @@ -70,7 +72,7 @@ class RoomSettingsFragment @Inject constructor( private lateinit var roomJoinRuleSharedActionViewModel: RoomJoinRuleSharedActionViewModel private val roomProfileArgs: RoomProfileArgs by args() - private val galleryOrCameraDialogHelper = GalleryOrCameraDialogHelper(this, colorProvider) + private val galleryOrCameraDialogHelper = GalleryOrCameraDialogHelper(this, colorProvider, clock) override fun getBinding(inflater: LayoutInflater, container: ViewGroup?): FragmentRoomSettingGenericBinding { return FragmentRoomSettingGenericBinding.inflate(inflater, container, false) @@ -198,7 +200,8 @@ class RoomSettingsFragment @Inject constructor( RoomSettingsAction.SetAvatarAction( RoomSettingsViewState.AvatarAction.UpdateAvatar( newAvatarUri = uri, - newAvatarFileName = getFilenameFromUri(requireContext(), uri) ?: UUID.randomUUID().toString()) + newAvatarFileName = getFilenameFromUri(requireContext(), uri) ?: UUID.randomUUID().toString() + ) ) ) } diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/settings/RoomSettingsViewEvents.kt b/vector/src/main/java/im/vector/app/features/roomprofile/settings/RoomSettingsViewEvents.kt index 83a768fb34..af297d962f 100644 --- a/vector/src/main/java/im/vector/app/features/roomprofile/settings/RoomSettingsViewEvents.kt +++ b/vector/src/main/java/im/vector/app/features/roomprofile/settings/RoomSettingsViewEvents.kt @@ -20,7 +20,7 @@ package im.vector.app.features.roomprofile.settings import im.vector.app.core.platform.VectorViewEvents /** - * Transient events for room settings screen + * Transient events for room settings screen. */ sealed class RoomSettingsViewEvents : VectorViewEvents { data class Failure(val throwable: Throwable) : RoomSettingsViewEvents() diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/settings/RoomSettingsViewModel.kt b/vector/src/main/java/im/vector/app/features/roomprofile/settings/RoomSettingsViewModel.kt index 8ad5bcdce6..ee04d22ddc 100644 --- a/vector/src/main/java/im/vector/app/features/roomprofile/settings/RoomSettingsViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/roomprofile/settings/RoomSettingsViewModel.kt @@ -35,6 +35,7 @@ import org.matrix.android.sdk.api.query.QueryStringValue import org.matrix.android.sdk.api.session.Session import org.matrix.android.sdk.api.session.events.model.EventType import org.matrix.android.sdk.api.session.events.model.toModel +import org.matrix.android.sdk.api.session.getRoom import org.matrix.android.sdk.api.session.homeserver.HomeServerCapabilities import org.matrix.android.sdk.api.session.room.model.RoomAvatarContent import org.matrix.android.sdk.api.session.room.model.RoomGuestAccessContent @@ -67,9 +68,9 @@ class RoomSettingsViewModel @AssistedInject constructor(@Assisted initialState: observeRoomAvatar() observeState() - val homeServerCapabilities = session.getHomeServerCapabilities() + val homeServerCapabilities = session.homeServerCapabilitiesService().getHomeServerCapabilities() val canUseRestricted = homeServerCapabilities - .isFeatureSupported(HomeServerCapabilities.ROOM_CAP_RESTRICTED, room.getRoomVersion()) + .isFeatureSupported(HomeServerCapabilities.ROOM_CAP_RESTRICTED, room.roomVersionService().getRoomVersion()) val restrictedSupport = homeServerCapabilities.isFeatureSupported(HomeServerCapabilities.ROOM_CAP_RESTRICTED) val couldUpgradeToRestricted = restrictedSupport == HomeServerCapabilities.RoomCapabilitySupport.SUPPORTED @@ -89,12 +90,13 @@ class RoomSettingsViewModel @AssistedInject constructor(@Assisted initialState: RoomSettingsViewState::newTopic, RoomSettingsViewState::newHistoryVisibility, RoomSettingsViewState::newRoomJoinRules, - RoomSettingsViewState::roomSummary) { avatarAction, - newName, - newTopic, - newHistoryVisibility, - newJoinRule, - asyncSummary -> + RoomSettingsViewState::roomSummary + ) { avatarAction, + newName, + newTopic, + newHistoryVisibility, + newJoinRule, + asyncSummary -> val summary = asyncSummary() setState { copy( @@ -129,14 +131,22 @@ class RoomSettingsViewModel @AssistedInject constructor(@Assisted initialState: canChangeAvatar = powerLevelsHelper.isUserAllowedToSend(session.myUserId, true, EventType.STATE_ROOM_AVATAR), canChangeName = powerLevelsHelper.isUserAllowedToSend(session.myUserId, true, EventType.STATE_ROOM_NAME), canChangeTopic = powerLevelsHelper.isUserAllowedToSend(session.myUserId, true, EventType.STATE_ROOM_TOPIC), - canChangeHistoryVisibility = powerLevelsHelper.isUserAllowedToSend(session.myUserId, true, - EventType.STATE_ROOM_HISTORY_VISIBILITY), - canChangeJoinRule = powerLevelsHelper.isUserAllowedToSend(session.myUserId, true, - EventType.STATE_ROOM_JOIN_RULES) && - powerLevelsHelper.isUserAllowedToSend(session.myUserId, true, - EventType.STATE_ROOM_GUEST_ACCESS), - canAddChildren = powerLevelsHelper.isUserAllowedToSend(session.myUserId, true, - EventType.STATE_SPACE_CHILD) + canChangeHistoryVisibility = powerLevelsHelper.isUserAllowedToSend( + session.myUserId, true, + EventType.STATE_ROOM_HISTORY_VISIBILITY + ), + canChangeJoinRule = powerLevelsHelper.isUserAllowedToSend( + session.myUserId, true, + EventType.STATE_ROOM_JOIN_RULES + ) && + powerLevelsHelper.isUserAllowedToSend( + session.myUserId, true, + EventType.STATE_ROOM_GUEST_ACCESS + ), + canAddChildren = powerLevelsHelper.isUserAllowedToSend( + session.myUserId, true, + EventType.STATE_SPACE_CHILD + ) ) setState { copy(actionPermissions = permissions) @@ -248,25 +258,25 @@ class RoomSettingsViewModel @AssistedInject constructor(@Assisted initialState: when (val avatarAction = state.avatarAction) { RoomSettingsViewState.AvatarAction.None -> Unit RoomSettingsViewState.AvatarAction.DeleteAvatar -> { - operationList.add { room.deleteAvatar() } + operationList.add { room.stateService().deleteAvatar() } } is RoomSettingsViewState.AvatarAction.UpdateAvatar -> { - operationList.add { room.updateAvatar(avatarAction.newAvatarUri, avatarAction.newAvatarFileName) } + operationList.add { room.stateService().updateAvatar(avatarAction.newAvatarUri, avatarAction.newAvatarFileName) } } } if (summary?.name != state.newName) { - operationList.add { room.updateName(state.newName ?: "") } + operationList.add { room.stateService().updateName(state.newName ?: "") } } if (summary?.topic != state.newTopic) { - operationList.add { room.updateTopic(state.newTopic ?: "") } + operationList.add { room.stateService().updateTopic(state.newTopic ?: "") } } if (state.newHistoryVisibility != null) { - operationList.add { room.updateHistoryReadability(state.newHistoryVisibility) } + operationList.add { room.stateService().updateHistoryReadability(state.newHistoryVisibility) } } if (state.newRoomJoinRules.hasChanged()) { - operationList.add { room.updateJoinRule(state.newRoomJoinRules.newJoinRules, state.newRoomJoinRules.newGuestAccess) } + operationList.add { room.stateService().updateJoinRule(state.newRoomJoinRules.newJoinRules, state.newRoomJoinRules.newGuestAccess) } } viewModelScope.launch { updateLoadingState(isLoading = true) diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/settings/RoomSettingsViewState.kt b/vector/src/main/java/im/vector/app/features/roomprofile/settings/RoomSettingsViewState.kt index 122e0034c6..fe7c984cb4 100644 --- a/vector/src/main/java/im/vector/app/features/roomprofile/settings/RoomSettingsViewState.kt +++ b/vector/src/main/java/im/vector/app/features/roomprofile/settings/RoomSettingsViewState.kt @@ -75,13 +75,13 @@ data class RoomSettingsViewState( fun getJoinRuleWording(stringProvider: StringProvider): String { return when (val joinRule = newRoomJoinRules.newJoinRules ?: currentRoomJoinRules) { - RoomJoinRules.INVITE -> { + RoomJoinRules.INVITE -> { stringProvider.getString(R.string.room_settings_room_access_private_title) } - RoomJoinRules.PUBLIC -> { + RoomJoinRules.PUBLIC -> { stringProvider.getString(R.string.room_settings_room_access_public_title) } - RoomJoinRules.KNOCK -> { + RoomJoinRules.KNOCK -> { stringProvider.getString(R.string.room_settings_room_access_entry_knock) } RoomJoinRules.RESTRICTED -> { diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/settings/historyvisibility/RoomHistoryVisibilitySharedActionViewModel.kt b/vector/src/main/java/im/vector/app/features/roomprofile/settings/historyvisibility/RoomHistoryVisibilitySharedActionViewModel.kt index c4f4892984..2f1fc086db 100644 --- a/vector/src/main/java/im/vector/app/features/roomprofile/settings/historyvisibility/RoomHistoryVisibilitySharedActionViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/roomprofile/settings/historyvisibility/RoomHistoryVisibilitySharedActionViewModel.kt @@ -20,4 +20,4 @@ import im.vector.app.core.platform.VectorSharedActionViewModel import javax.inject.Inject class RoomHistoryVisibilitySharedActionViewModel @Inject constructor() : - VectorSharedActionViewModel() + VectorSharedActionViewModel() diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/settings/historyvisibility/RoomHistoryVisibilityViewModel.kt b/vector/src/main/java/im/vector/app/features/roomprofile/settings/historyvisibility/RoomHistoryVisibilityViewModel.kt index 5580156918..e99a8d8a0b 100644 --- a/vector/src/main/java/im/vector/app/features/roomprofile/settings/historyvisibility/RoomHistoryVisibilityViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/roomprofile/settings/historyvisibility/RoomHistoryVisibilityViewModel.kt @@ -19,4 +19,4 @@ package im.vector.app.features.roomprofile.settings.historyvisibility import im.vector.app.core.ui.bottomsheet.BottomSheetGenericViewModel class RoomHistoryVisibilityViewModel(initialState: RoomHistoryVisibilityState) : - BottomSheetGenericViewModel(initialState) + BottomSheetGenericViewModel(initialState) diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/settings/joinrule/RoomJoinRuleActivity.kt b/vector/src/main/java/im/vector/app/features/roomprofile/settings/joinrule/RoomJoinRuleActivity.kt index 7099d30862..d6e74c546d 100644 --- a/vector/src/main/java/im/vector/app/features/roomprofile/settings/joinrule/RoomJoinRuleActivity.kt +++ b/vector/src/main/java/im/vector/app/features/roomprofile/settings/joinrule/RoomJoinRuleActivity.kt @@ -121,7 +121,8 @@ class RoomJoinRuleActivity : VectorBaseActivity() { supportFragmentManager.commitTransaction { setCustomAnimations(R.anim.fade_in, R.anim.fade_out, R.anim.fade_in, R.anim.fade_out) val tag = RoomJoinRuleChooseRestrictedFragment::class.simpleName - replace(views.simpleFragmentContainer.id, + replace( + views.simpleFragmentContainer.id, RoomJoinRuleChooseRestrictedFragment::class.java, this@RoomJoinRuleActivity.roomProfileArgs.toMvRxBundle(), tag diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/settings/joinrule/RoomJoinRuleSharedActionViewModel.kt b/vector/src/main/java/im/vector/app/features/roomprofile/settings/joinrule/RoomJoinRuleSharedActionViewModel.kt index 971d71e0df..fd1fb4d61e 100644 --- a/vector/src/main/java/im/vector/app/features/roomprofile/settings/joinrule/RoomJoinRuleSharedActionViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/roomprofile/settings/joinrule/RoomJoinRuleSharedActionViewModel.kt @@ -20,4 +20,4 @@ import im.vector.app.core.platform.VectorSharedActionViewModel import javax.inject.Inject class RoomJoinRuleSharedActionViewModel @Inject constructor() : - VectorSharedActionViewModel() + VectorSharedActionViewModel() diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/settings/joinrule/RoomJoinRuleViewModel.kt b/vector/src/main/java/im/vector/app/features/roomprofile/settings/joinrule/RoomJoinRuleViewModel.kt index 1ff374bf5b..e528c41676 100644 --- a/vector/src/main/java/im/vector/app/features/roomprofile/settings/joinrule/RoomJoinRuleViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/roomprofile/settings/joinrule/RoomJoinRuleViewModel.kt @@ -19,4 +19,4 @@ package im.vector.app.features.roomprofile.settings.joinrule import im.vector.app.core.ui.bottomsheet.BottomSheetGenericViewModel class RoomJoinRuleViewModel(initialState: RoomJoinRuleState) : - BottomSheetGenericViewModel(initialState) + BottomSheetGenericViewModel(initialState) diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/settings/joinrule/advanced/RoomJoinRuleChooseRestrictedViewModel.kt b/vector/src/main/java/im/vector/app/features/roomprofile/settings/joinrule/advanced/RoomJoinRuleChooseRestrictedViewModel.kt index f1897761b2..87dc0d2da9 100644 --- a/vector/src/main/java/im/vector/app/features/roomprofile/settings/joinrule/advanced/RoomJoinRuleChooseRestrictedViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/roomprofile/settings/joinrule/advanced/RoomJoinRuleChooseRestrictedViewModel.kt @@ -40,7 +40,10 @@ import org.matrix.android.sdk.api.query.QueryStringValue import org.matrix.android.sdk.api.session.Session import org.matrix.android.sdk.api.session.events.model.EventType import org.matrix.android.sdk.api.session.events.model.toModel +import org.matrix.android.sdk.api.session.getRoom +import org.matrix.android.sdk.api.session.getRoomSummary import org.matrix.android.sdk.api.session.homeserver.HomeServerCapabilities +import org.matrix.android.sdk.api.session.room.getStateEvent import org.matrix.android.sdk.api.session.room.model.Membership import org.matrix.android.sdk.api.session.room.model.RoomJoinRules import org.matrix.android.sdk.api.session.room.model.RoomJoinRulesContent @@ -97,11 +100,11 @@ class RoomJoinRuleChooseRestrictedViewModel @AssistedInject constructor( } } - val homeServerCapabilities = session.getHomeServerCapabilities() + val homeServerCapabilities = session.homeServerCapabilitiesService().getHomeServerCapabilities() var safeRule: RoomJoinRules = joinRulesContent?.joinRules ?: RoomJoinRules.INVITE // server is not really checking that, just to be sure let's check val restrictedSupportedByThisVersion = homeServerCapabilities - .isFeatureSupported(HomeServerCapabilities.ROOM_CAP_RESTRICTED, room.getRoomVersion()) + .isFeatureSupported(HomeServerCapabilities.ROOM_CAP_RESTRICTED, room.roomVersionService().getRoomVersion()) if (safeRule == RoomJoinRules.RESTRICTED && !restrictedSupportedByThisVersion) { safeRule = RoomJoinRules.INVITE @@ -189,9 +192,9 @@ class RoomJoinRuleChooseRestrictedViewModel @AssistedInject constructor( viewModelScope.launch { try { when (state.currentRoomJoinRules) { - RoomJoinRules.PUBLIC -> room.setJoinRulePublic() - RoomJoinRules.INVITE -> room.setJoinRuleInviteOnly() - RoomJoinRules.RESTRICTED -> room.setJoinRuleRestricted(state.updatedAllowList.map { it.id }) + RoomJoinRules.PUBLIC -> room.stateService().setJoinRulePublic() + RoomJoinRules.INVITE -> room.stateService().setJoinRuleInviteOnly() + RoomJoinRules.RESTRICTED -> room.stateService().setJoinRuleRestricted(state.updatedAllowList.map { it.id }) RoomJoinRules.KNOCK, RoomJoinRules.PRIVATE, null -> { @@ -292,7 +295,7 @@ class RoomJoinRuleChooseRestrictedViewModel @AssistedInject constructor( setState { copy(updatingStatus = Loading()) } viewModelScope.launch { try { - room.setJoinRuleRestricted(candidates.map { it.id }) + room.stateService().setJoinRuleRestricted(candidates.map { it.id }) setState { copy(updatingStatus = Success(Unit)) } } catch (failure: Throwable) { setState { copy(updatingStatus = Fail(failure)) } @@ -355,7 +358,7 @@ class RoomJoinRuleChooseRestrictedViewModel @AssistedInject constructor( viewModelScope.launch { if (vectorPreferences.developerMode()) { // in developer mode we let you choose any room or space to restrict to - val filteredCandidates = session.getRoomSummaries( + val filteredCandidates = session.roomService().getRoomSummaries( roomSummaryQueryParams { excludeType = null displayName = QueryStringValue.Contains(action.filter, QueryStringValue.Case.INSENSITIVE) diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/uploads/RoomUploadsFragment.kt b/vector/src/main/java/im/vector/app/features/roomprofile/uploads/RoomUploadsFragment.kt index 6a115ad272..721f99178f 100644 --- a/vector/src/main/java/im/vector/app/features/roomprofile/uploads/RoomUploadsFragment.kt +++ b/vector/src/main/java/im/vector/app/features/roomprofile/uploads/RoomUploadsFragment.kt @@ -30,6 +30,7 @@ import com.google.android.material.tabs.TabLayoutMediator import im.vector.app.R import im.vector.app.core.intent.getMimeTypeFromUri import im.vector.app.core.platform.VectorBaseFragment +import im.vector.app.core.time.Clock import im.vector.app.core.utils.saveMedia import im.vector.app.core.utils.shareMedia import im.vector.app.databinding.FragmentRoomUploadsBinding @@ -43,7 +44,8 @@ import javax.inject.Inject class RoomUploadsFragment @Inject constructor( private val avatarRenderer: AvatarRenderer, - private val notificationUtils: NotificationUtils + private val notificationUtils: NotificationUtils, + private val clock: Clock, ) : VectorBaseFragment() { private val roomProfileArgs: RoomProfileArgs by args() @@ -88,7 +90,8 @@ class RoomUploadsFragment @Inject constructor( file = it.file, title = it.title, mediaMimeType = getMimeTypeFromUri(requireContext(), it.file.toUri()), - notificationUtils = notificationUtils + notificationUtils = notificationUtils, + currentTimeMillis = clock.epochMillis() ) }.onFailure { failure -> if (!isAdded) return@onFailure diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/uploads/RoomUploadsViewModel.kt b/vector/src/main/java/im/vector/app/features/roomprofile/uploads/RoomUploadsViewModel.kt index c9aaca4373..9ea6fd2dc2 100644 --- a/vector/src/main/java/im/vector/app/features/roomprofile/uploads/RoomUploadsViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/roomprofile/uploads/RoomUploadsViewModel.kt @@ -28,6 +28,7 @@ import im.vector.app.core.di.hiltMavericksViewModelFactory import im.vector.app.core.platform.VectorViewModel import kotlinx.coroutines.launch import org.matrix.android.sdk.api.session.Session +import org.matrix.android.sdk.api.session.getRoom import org.matrix.android.sdk.api.session.room.model.message.MessageType import org.matrix.android.sdk.flow.flow import org.matrix.android.sdk.flow.unwrap @@ -72,7 +73,7 @@ class RoomUploadsViewModel @AssistedInject constructor( viewModelScope.launch { try { - val result = room.getUploads(20, token) + val result = room.uploadsService().getUploads(20, token) token = result.nextToken @@ -116,7 +117,8 @@ class RoomUploadsViewModel @AssistedInject constructor( viewModelScope.launch { val event = try { val file = session.fileService().downloadFile( - messageContent = action.uploadEvent.contentWithAttachmentContent) + messageContent = action.uploadEvent.contentWithAttachmentContent + ) RoomUploadsViewEvents.FileReadyForSharing(file) } catch (failure: Throwable) { RoomUploadsViewEvents.Failure(failure) @@ -129,7 +131,8 @@ class RoomUploadsViewModel @AssistedInject constructor( viewModelScope.launch { val event = try { val file = session.fileService().downloadFile( - messageContent = action.uploadEvent.contentWithAttachmentContent) + messageContent = action.uploadEvent.contentWithAttachmentContent + ) RoomUploadsViewEvents.FileReadyForSaving(file, action.uploadEvent.contentWithAttachmentContent.body) } catch (failure: Throwable) { RoomUploadsViewEvents.Failure(failure) diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/uploads/files/UploadsFileController.kt b/vector/src/main/java/im/vector/app/features/roomprofile/uploads/files/UploadsFileController.kt index 4d45c2ccfc..b741b5c652 100644 --- a/vector/src/main/java/im/vector/app/features/roomprofile/uploads/files/UploadsFileController.kt +++ b/vector/src/main/java/im/vector/app/features/roomprofile/uploads/files/UploadsFileController.kt @@ -68,9 +68,13 @@ class UploadsFileController @Inject constructor( uploadsFileItem { id(uploadEvent.eventId) title(uploadEvent.contentWithAttachmentContent.body) - subtitle(host.stringProvider.getString(R.string.uploads_files_subtitle, - uploadEvent.senderInfo.disambiguatedDisplayName, - host.dateFormatter.format(uploadEvent.root.originServerTs, DateFormatKind.DEFAULT_DATE_AND_TIME))) + subtitle( + host.stringProvider.getString( + R.string.uploads_files_subtitle, + uploadEvent.senderInfo.disambiguatedDisplayName, + host.dateFormatter.format(uploadEvent.root.originServerTs, DateFormatKind.DEFAULT_DATE_AND_TIME) + ) + ) listener(object : UploadsFileItem.Listener { override fun onItemClicked() { host.listener?.onOpenClicked(uploadEvent) diff --git a/vector/src/main/java/im/vector/app/features/session/SessionScopedProperty.kt b/vector/src/main/java/im/vector/app/features/session/SessionScopedProperty.kt index e6a84a41d8..64d49800c8 100644 --- a/vector/src/main/java/im/vector/app/features/session/SessionScopedProperty.kt +++ b/vector/src/main/java/im/vector/app/features/session/SessionScopedProperty.kt @@ -23,9 +23,9 @@ import kotlin.reflect.KProperty * This is a simple hack for having some Session scope dependencies. * Probably a temporary solution waiting for refactoring the Dagger management of Session. * You should use it with an extension property : - val Session.myProperty: MyProperty by SessionScopedProperty { - init code - } +val Session.myProperty: MyProperty by SessionScopedProperty { +init code +} * */ class SessionScopedProperty(val initializer: (Session) -> T) { diff --git a/vector/src/main/java/im/vector/app/features/session/VectorSessionStore.kt b/vector/src/main/java/im/vector/app/features/session/VectorSessionStore.kt index 79183e1808..9466fc1459 100644 --- a/vector/src/main/java/im/vector/app/features/session/VectorSessionStore.kt +++ b/vector/src/main/java/im/vector/app/features/session/VectorSessionStore.kt @@ -26,7 +26,7 @@ import org.matrix.android.sdk.api.util.md5 /** * User session scoped storage for: - * - messaging use case (Enum/String) + * - messaging use case (Enum/String). */ class VectorSessionStore constructor( context: Context, diff --git a/vector/src/main/java/im/vector/app/features/settings/BackgroundSyncMode.kt b/vector/src/main/java/im/vector/app/features/settings/BackgroundSyncMode.kt index 9d8b5755b0..1712b8cd27 100644 --- a/vector/src/main/java/im/vector/app/features/settings/BackgroundSyncMode.kt +++ b/vector/src/main/java/im/vector/app/features/settings/BackgroundSyncMode.kt @@ -17,7 +17,7 @@ package im.vector.app.features.settings /** - * Different strategies for Background sync, only applicable to F-Droid version of the app + * Different strategies for Background sync, only applicable to F-Droid version of the app. */ enum class BackgroundSyncMode { /** @@ -34,7 +34,7 @@ enum class BackgroundSyncMode { FDROID_BACKGROUND_SYNC_MODE_FOR_REALTIME, /** - * The app won't sync in background + * The app won't sync in background. */ FDROID_BACKGROUND_SYNC_MODE_DISABLED; diff --git a/vector/src/main/java/im/vector/app/features/settings/FontScale.kt b/vector/src/main/java/im/vector/app/features/settings/FontScale.kt index ad678ec49d..c4ea730afd 100644 --- a/vector/src/main/java/im/vector/app/features/settings/FontScale.kt +++ b/vector/src/main/java/im/vector/app/features/settings/FontScale.kt @@ -23,7 +23,7 @@ import im.vector.app.R import im.vector.app.core.di.DefaultSharedPreferences /** - * Object to manage the Font Scale choice of the user + * Object to manage the Font Scale choice of the user. */ object FontScale { // Key for the SharedPrefs @@ -51,7 +51,7 @@ object FontScale { private val normalFontScaleValue = fontScaleValues[2] /** - * Get the font scale value from SharedPrefs. Init the SharedPrefs if necessary + * Get the font scale value from SharedPrefs. Init the SharedPrefs if necessary. * * @return the font scale value */ @@ -76,7 +76,7 @@ object FontScale { } /** - * Store the font scale vale + * Store the font scale value. * * @param fontScaleValue the font scale value to store */ diff --git a/vector/src/main/java/im/vector/app/features/settings/SecretsSynchronisationInfo.kt b/vector/src/main/java/im/vector/app/features/settings/SecretsSynchronisationInfo.kt index e21366db02..d667a4f589 100644 --- a/vector/src/main/java/im/vector/app/features/settings/SecretsSynchronisationInfo.kt +++ b/vector/src/main/java/im/vector/app/features/settings/SecretsSynchronisationInfo.kt @@ -45,7 +45,7 @@ fun Session.liveSecretSynchronisationInfo(): Flow { sessionFlow.liveCrossSigningPrivateKeys() ) { _, crossSigningInfo, pInfo -> // first check if 4S is already setup - val is4SSetup = sharedSecretStorageService.isRecoverySetup() + val is4SSetup = sharedSecretStorageService().isRecoverySetup() val isCrossSigningEnabled = crossSigningInfo.getOrNull() != null val isCrossSigningTrusted = crossSigningInfo.getOrNull()?.isTrusted() == true val allPrivateKeysKnown = pInfo.getOrNull()?.allKnown().orFalse() @@ -63,7 +63,7 @@ fun Session.liveSecretSynchronisationInfo(): Flow { allPrivateKeysKnown = allPrivateKeysKnown, megolmBackupAvailable = megolmBackupAvailable, megolmSecretKnown = megolmKeyKnown, - isMegolmKeyIn4S = sharedSecretStorageService.isMegolmKeyInBackup() + isMegolmKeyIn4S = sharedSecretStorageService().isMegolmKeyInBackup() ) } .distinctUntilChanged() diff --git a/vector/src/main/java/im/vector/app/features/settings/VectorLocale.kt b/vector/src/main/java/im/vector/app/features/settings/VectorLocale.kt index f558ba28c6..3fb3d3f7c8 100644 --- a/vector/src/main/java/im/vector/app/features/settings/VectorLocale.kt +++ b/vector/src/main/java/im/vector/app/features/settings/VectorLocale.kt @@ -29,7 +29,7 @@ import java.util.IllformedLocaleException import java.util.Locale /** - * Object to manage the Locale choice of the user + * Object to manage the Locale choice of the user. */ object VectorLocale { private const val APPLICATION_LOCALE_COUNTRY_KEY = "APPLICATION_LOCALE_COUNTRY_KEY" @@ -42,12 +42,12 @@ object VectorLocale { private const val ISO_15924_LATN = "Latn" /** - * The cache of supported application languages + * The cache of supported application languages. */ private val supportedLocales = mutableListOf() /** - * Provides the current application locale + * Provides the current application locale. */ var applicationLocale = defaultLocale private set @@ -55,14 +55,15 @@ object VectorLocale { private lateinit var context: Context /** - * Init this object + * Init this object. */ fun init(context: Context) { this.context = context val preferences = DefaultSharedPreferences.getInstance(context) if (preferences.contains(APPLICATION_LOCALE_LANGUAGE_KEY)) { - applicationLocale = Locale(preferences.getString(APPLICATION_LOCALE_LANGUAGE_KEY, "")!!, + applicationLocale = Locale( + preferences.getString(APPLICATION_LOCALE_LANGUAGE_KEY, "")!!, preferences.getString(APPLICATION_LOCALE_COUNTRY_KEY, "")!!, preferences.getString(APPLICATION_LOCALE_VARIANT_KEY, "")!! ) @@ -117,7 +118,7 @@ object VectorLocale { } /** - * Get String from a locale + * Get String from a locale. * * @param context the context * @param locale the locale @@ -137,7 +138,7 @@ object VectorLocale { } /** - * Init the supported application locales list + * Init the supported application locales list. */ private fun initApplicationLocales() { val knownLocalesSet = HashSet>() @@ -188,7 +189,7 @@ object VectorLocale { } /** - * Convert a locale to a string + * Convert a locale to a string. * * @param locale the locale to convert * @return the string @@ -211,7 +212,7 @@ object VectorLocale { } /** - * Information about the locale in the current locale + * Information about the locale in the current locale. * * @param locale the locale to get info from * @return the string 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 195072465b..c841c6a0af 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 @@ -26,6 +26,7 @@ import com.squareup.seismic.ShakeDetector import im.vector.app.BuildConfig import im.vector.app.R import im.vector.app.core.di.DefaultSharedPreferences +import im.vector.app.core.time.Clock import im.vector.app.features.disclaimer.SHARED_PREF_KEY import im.vector.app.features.homeserver.ServerUrlsRepository import im.vector.app.features.themes.ThemeUtils @@ -33,7 +34,10 @@ import org.matrix.android.sdk.api.extensions.tryOrNull import timber.log.Timber import javax.inject.Inject -class VectorPreferences @Inject constructor(private val context: Context) { +class VectorPreferences @Inject constructor( + private val context: Context, + private val clock: Clock, +) { companion object { const val SETTINGS_HELP_PREFERENCE_KEY = "SETTINGS_HELP_PREFERENCE_KEY" @@ -207,6 +211,9 @@ class VectorPreferences @Inject constructor(private val context: Context) { const val SETTINGS_LABS_ENABLE_THREAD_MESSAGES = "SETTINGS_LABS_ENABLE_THREAD_MESSAGES_FINAL" const val SETTINGS_THREAD_MESSAGES_SYNCED = "SETTINGS_THREAD_MESSAGES_SYNCED" + // This key will be used to enable user for displaying live user info or not. + const val SETTINGS_TIMELINE_SHOW_LIVE_SENDER_INFO = "SETTINGS_TIMELINE_SHOW_LIVE_SENDER_INFO" + // Possible values for TAKE_PHOTO_VIDEO_MODE const val TAKE_PHOTO_VIDEO_MODE_ALWAYS_ASK = 0 const val TAKE_PHOTO_VIDEO_MODE_PHOTO = 1 @@ -392,7 +399,7 @@ class VectorPreferences @Inject constructor(private val context: Context) { } /** - * Tells if the timestamp must be displayed in 12h format + * Tells if the timestamp must be displayed in 12h format. * * @return true if the time must be displayed in 12h format */ @@ -437,14 +444,14 @@ class VectorPreferences @Inject constructor(private val context: Context) { } /** - * Show all rooms in room directory + * Show all rooms in room directory. */ fun showAllPublicRooms(): Boolean { return defaultPrefs.getBoolean(SETTINGS_ROOM_DIRECTORY_SHOW_ALL_PUBLIC_ROOMS, false) } /** - * Tells which compression level to use by default + * Tells which compression level to use by default. * * @return the selected compression level */ @@ -453,7 +460,7 @@ class VectorPreferences @Inject constructor(private val context: Context) { } /** - * Tells which media source to use by default + * Tells which media source to use by default. * * @return the selected media source */ @@ -483,7 +490,7 @@ class VectorPreferences @Inject constructor(private val context: Context) { } /** - * Update the notification ringtone + * Update the notification ringtone. * * @param uri the new notification ringtone, or null for no RingTone */ @@ -507,7 +514,7 @@ class VectorPreferences @Inject constructor(private val context: Context) { } /** - * Provides the selected notification ring tone + * Provides the selected notification ring tone. * * @return the selected ring tone or null for no RingTone */ @@ -539,7 +546,7 @@ class VectorPreferences @Inject constructor(private val context: Context) { } /** - * Provide the notification ringtone filename + * Provide the notification ringtone filename. * * @return the filename or null if "None" is selected */ @@ -561,7 +568,7 @@ class VectorPreferences @Inject constructor(private val context: Context) { } /** - * Enable or disable the lazy loading + * Enable or disable the lazy loading. * * @param newValue true to enable lazy loading, false to disable it */ @@ -572,7 +579,7 @@ class VectorPreferences @Inject constructor(private val context: Context) { } /** - * Tells if the lazy loading is enabled + * Tells if the lazy loading is enabled. * * @return true if the lazy loading of room members is enabled */ @@ -582,7 +589,6 @@ class VectorPreferences @Inject constructor(private val context: Context) { /** * User explicitly refuses the lazy loading. - * */ fun setUserRefuseLazyLoading() { defaultPrefs.edit { @@ -591,7 +597,7 @@ class VectorPreferences @Inject constructor(private val context: Context) { } /** - * Tells if the user has explicitly refused the lazy loading + * Tells if the user has explicitly refused the lazy loading. * * @return true if the user has explicitly refuse the lazy loading of room members */ @@ -600,7 +606,7 @@ class VectorPreferences @Inject constructor(private val context: Context) { } /** - * Tells if the data save mode is enabled + * Tells if the data save mode is enabled. * * @return true if the data save mode is enabled */ @@ -618,7 +624,7 @@ class VectorPreferences @Inject constructor(private val context: Context) { } /** - * Tells if the application is started on boot + * Tells if the application is started on boot. * * @return true if the application must be started on boot */ @@ -627,7 +633,7 @@ class VectorPreferences @Inject constructor(private val context: Context) { } /** - * Tells if the application is started on boot + * Tells if the application is started on boot. * * @param value true to start the application on boot */ @@ -664,9 +670,9 @@ class VectorPreferences @Inject constructor(private val context: Context) { */ fun getMinMediasLastAccessTime(): Long { return when (getSelectedMediasSavingPeriod()) { - MEDIA_SAVING_3_DAYS -> System.currentTimeMillis() / 1000 - 3 * 24 * 60 * 60 - MEDIA_SAVING_1_WEEK -> System.currentTimeMillis() / 1000 - 7 * 24 * 60 * 60 - MEDIA_SAVING_1_MONTH -> System.currentTimeMillis() / 1000 - 30 * 24 * 60 * 60 + MEDIA_SAVING_3_DAYS -> clock.epochMillis() / 1000 - 3 * 24 * 60 * 60 + MEDIA_SAVING_1_WEEK -> clock.epochMillis() / 1000 - 7 * 24 * 60 * 60 + MEDIA_SAVING_1_MONTH -> clock.epochMillis() / 1000 - 30 * 24 * 60 * 60 MEDIA_SAVING_FOREVER -> 0 else -> 0 } @@ -688,14 +694,14 @@ class VectorPreferences @Inject constructor(private val context: Context) { } /** - * Fix some migration issues + * Fix some migration issues. */ fun fixMigrationIssues() { // Nothing to do for the moment } /** - * Tells if the markdown is enabled + * Tells if the markdown is enabled. * * @return true if the markdown is enabled */ @@ -715,14 +721,14 @@ class VectorPreferences @Inject constructor(private val context: Context) { } /** - * Tells if a confirmation dialog should be displayed before staring a call + * Tells if a confirmation dialog should be displayed before staring a call. */ fun preventAccidentalCall(): Boolean { return defaultPrefs.getBoolean(SETTINGS_CALL_PREVENT_ACCIDENTAL_CALL_KEY, false) } /** - * Tells if the read receipts should be shown + * Tells if the read receipts should be shown. * * @return true if the read receipts should be shown */ @@ -731,7 +737,7 @@ class VectorPreferences @Inject constructor(private val context: Context) { } /** - * Tells if the redacted message should be shown + * Tells if the redacted message should be shown. * * @return true if the redacted should be shown */ @@ -740,7 +746,7 @@ class VectorPreferences @Inject constructor(private val context: Context) { } /** - * Tells if the help on room list should be shown + * Tells if the help on room list should be shown. * * @return true if the help on room list should be shown */ @@ -749,7 +755,7 @@ class VectorPreferences @Inject constructor(private val context: Context) { } /** - * Prevent help on room list to be shown again + * Prevent help on room list to be shown again. */ fun neverShowLongClickOnRoomHelpAgain() { defaultPrefs.edit { @@ -758,7 +764,7 @@ class VectorPreferences @Inject constructor(private val context: Context) { } /** - * Tells if the message timestamps must be always shown + * Tells if the message timestamps must be always shown. * * @return true if the message timestamps must be always shown */ @@ -767,7 +773,7 @@ class VectorPreferences @Inject constructor(private val context: Context) { } /** - * Tells if the typing notifications should be sent + * Tells if the typing notifications should be sent. * * @return true to send the typing notifs */ @@ -776,7 +782,7 @@ class VectorPreferences @Inject constructor(private val context: Context) { } /** - * Tells of the missing notifications rooms must be displayed at left (home screen) + * Tells of the missing notifications rooms must be displayed at left (home screen). * * @return true to move the missed notifications to the left side */ @@ -785,7 +791,7 @@ class VectorPreferences @Inject constructor(private val context: Context) { } /** - * Tells of the unread rooms must be displayed at left (home screen) + * Tells of the unread rooms must be displayed at left (home screen). * * @return true to move the unread room to the left side */ @@ -794,7 +800,7 @@ class VectorPreferences @Inject constructor(private val context: Context) { } /** - * Tells if the phone must vibrate when mentioning + * Tells if the phone must vibrate when mentioning. * * @return true */ @@ -822,7 +828,7 @@ class VectorPreferences @Inject constructor(private val context: Context) { } /** - * Tells if the user wants to see URL previews in the timeline + * Tells if the user wants to see URL previews in the timeline. * * @return true if the user wants to see URL previews in the timeline */ @@ -831,7 +837,7 @@ class VectorPreferences @Inject constructor(private val context: Context) { } /** - * Tells if media should be previewed before sending + * Tells if media should be previewed before sending. * * @return true to preview media */ @@ -840,7 +846,7 @@ class VectorPreferences @Inject constructor(private val context: Context) { } /** - * Tells if message should be send by pressing enter on the soft keyboard + * Tells if message should be send by pressing enter on the soft keyboard. * * @return true to send message with enter */ @@ -872,8 +878,10 @@ class VectorPreferences @Inject constructor(private val context: Context) { * @return true if user should always appear offline */ fun userAlwaysAppearsOffline(): Boolean { - return defaultPrefs.getBoolean(SETTINGS_PRESENCE_USER_ALWAYS_APPEARS_OFFLINE, - getDefault(R.bool.settings_presence_user_always_appears_offline_default)) + return defaultPrefs.getBoolean( + SETTINGS_PRESENCE_USER_ALWAYS_APPEARS_OFFLINE, + getDefault(R.bool.settings_presence_user_always_appears_offline_default) + ) } /** @@ -913,7 +921,7 @@ class VectorPreferences @Inject constructor(private val context: Context) { } /** - * The user does not allow screenshots of the application + * The user does not allow screenshots of the application. */ fun useFlagSecure(): Boolean { return defaultPrefs.getBoolean(SETTINGS_SECURITY_USE_FLAG_SECURE, false) @@ -940,7 +948,7 @@ class VectorPreferences @Inject constructor(private val context: Context) { } /** - * Return true if Pin code is disabled, or if user set the settings to see full notification content + * Return true if Pin code is disabled, or if user set the settings to see full notification content. */ fun useCompleteNotificationFormat(): Boolean { return !useFlagPinCode() || @@ -1005,9 +1013,11 @@ class VectorPreferences @Inject constructor(private val context: Context) { } fun prefSpacesShowAllRoomInHome(): Boolean { - return defaultPrefs.getBoolean(SETTINGS_PREF_SPACE_SHOW_ALL_ROOM_IN_HOME, + return defaultPrefs.getBoolean( + SETTINGS_PREF_SPACE_SHOW_ALL_ROOM_IN_HOME, // migration of old property - !labsSpacesOnlyOrphansInHome()) + !labsSpacesOnlyOrphansInHome() + ) } /* @@ -1032,14 +1042,14 @@ class VectorPreferences @Inject constructor(private val context: Context) { } /** - * Indicates whether or not thread messages are enabled + * Indicates whether or not thread messages are enabled. */ fun areThreadMessagesEnabled(): Boolean { return defaultPrefs.getBoolean(SETTINGS_LABS_ENABLE_THREAD_MESSAGES, getDefault(R.bool.settings_labs_thread_messages_default)) } /** - * Manually sets thread messages enabled, useful for migrating users from io.element.thread + * Manually sets thread messages enabled, useful for migrating users from io.element.thread. */ fun setThreadMessagesEnabled() { defaultPrefs @@ -1049,15 +1059,15 @@ class VectorPreferences @Inject constructor(private val context: Context) { } /** - * Indicates whether or not the user will be notified about the new thread support - * We should notify the user only if he had old thread support enabled + * Indicates whether or not the user will be notified about the new thread support. + * We should notify the user only if he had old thread support enabled. */ fun shouldNotifyUserAboutThreads(): Boolean { return defaultPrefs.getBoolean(SETTINGS_LABS_ENABLE_THREAD_MESSAGES_OLD_CLIENTS, false) } /** - * Indicates that the user have been notified about threads migration + * Indicates that the user have been notified about threads migration. */ fun userNotifiedAboutThreads() { defaultPrefs @@ -1075,7 +1085,7 @@ class VectorPreferences @Inject constructor(private val context: Context) { } /** - * Indicates that there no longer threads migration needed + * Indicates that there no longer threads migration needed. */ fun setShouldMigrateThreads(shouldMigrate: Boolean) { defaultPrefs @@ -1083,4 +1093,8 @@ class VectorPreferences @Inject constructor(private val context: Context) { .putBoolean(SETTINGS_THREAD_MESSAGES_SYNCED, shouldMigrate) .apply() } + + fun showLiveSenderInfo(): Boolean { + return defaultPrefs.getBoolean(SETTINGS_TIMELINE_SHOW_LIVE_SENDER_INFO, getDefault(R.bool.settings_timeline_show_live_sender_info_default)) + } } diff --git a/vector/src/main/java/im/vector/app/features/settings/VectorSettingsActivity.kt b/vector/src/main/java/im/vector/app/features/settings/VectorSettingsActivity.kt index 725c4ceabc..dddda261cd 100755 --- a/vector/src/main/java/im/vector/app/features/settings/VectorSettingsActivity.kt +++ b/vector/src/main/java/im/vector/app/features/settings/VectorSettingsActivity.kt @@ -77,10 +77,12 @@ class VectorSettingsActivity : VectorBaseActivity SettingsActivityPayload.SecurityPrivacy -> replaceFragment(views.vectorSettingsPage, VectorSettingsSecurityPrivacyFragment::class.java, null, FRAGMENT_TAG) SettingsActivityPayload.SecurityPrivacyManageSessions -> - replaceFragment(views.vectorSettingsPage, + replaceFragment( + views.vectorSettingsPage, VectorSettingsDevicesFragment::class.java, null, - FRAGMENT_TAG) + FRAGMENT_TAG + ) SettingsActivityPayload.Notifications -> { requestHighlightPreferenceKeyOnResume(VectorPreferences.SETTINGS_ENABLE_THIS_DEVICE_PREFERENCE_KEY) replaceFragment(views.vectorSettingsPage, VectorSettingsNotificationPreferenceFragment::class.java, null, FRAGMENT_TAG) @@ -161,7 +163,8 @@ class VectorSettingsActivity : VectorBaseActivity } companion object { - fun getIntent(context: Context, directAccess: Int) = Companion.getIntent(context, when (directAccess) { + fun getIntent(context: Context, directAccess: Int) = Companion.getIntent( + context, when (directAccess) { EXTRA_DIRECT_ACCESS_ROOT -> SettingsActivityPayload.Root EXTRA_DIRECT_ACCESS_ADVANCED_SETTINGS -> SettingsActivityPayload.AdvancedSettings EXTRA_DIRECT_ACCESS_SECURITY_PRIVACY -> SettingsActivityPayload.SecurityPrivacy @@ -173,7 +176,8 @@ class VectorSettingsActivity : VectorBaseActivity Timber.w("Unknown directAccess: $directAccess defaulting to Root") SettingsActivityPayload.Root } - }) + } + ) fun getIntent(context: Context, payload: SettingsActivityPayload) = Intent(context, VectorSettingsActivity::class.java) .applyPayload(payload) diff --git a/vector/src/main/java/im/vector/app/features/settings/VectorSettingsGeneralFragment.kt b/vector/src/main/java/im/vector/app/features/settings/VectorSettingsGeneralFragment.kt index 7060bd67fd..99acea79df 100644 --- a/vector/src/main/java/im/vector/app/features/settings/VectorSettingsGeneralFragment.kt +++ b/vector/src/main/java/im/vector/app/features/settings/VectorSettingsGeneralFragment.kt @@ -45,6 +45,7 @@ import im.vector.app.core.preference.UserAvatarPreference import im.vector.app.core.preference.VectorPreference import im.vector.app.core.preference.VectorSwitchPreference import im.vector.app.core.resources.ColorProvider +import im.vector.app.core.time.Clock import im.vector.app.core.utils.TextUtils import im.vector.app.core.utils.getSizeOfFiles import im.vector.app.core.utils.toast @@ -64,6 +65,7 @@ import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.launch import kotlinx.coroutines.withContext import org.matrix.android.sdk.api.failure.isInvalidPassword +import org.matrix.android.sdk.api.session.getUser import org.matrix.android.sdk.api.session.integrationmanager.IntegrationManagerConfig import org.matrix.android.sdk.api.session.integrationmanager.IntegrationManagerService import org.matrix.android.sdk.flow.flow @@ -73,7 +75,8 @@ import java.util.UUID import javax.inject.Inject class VectorSettingsGeneralFragment @Inject constructor( - colorProvider: ColorProvider + colorProvider: ColorProvider, + clock: Clock, ) : VectorSettingsBaseFragment(), GalleryOrCameraDialogHelper.Listener { @@ -81,7 +84,7 @@ class VectorSettingsGeneralFragment @Inject constructor( override var titleRes = R.string.settings_general_title override val preferenceXmlRes = R.xml.vector_settings_general - private val galleryOrCameraDialogHelper = GalleryOrCameraDialogHelper(this, colorProvider) + private val galleryOrCameraDialogHelper = GalleryOrCameraDialogHelper(this, colorProvider, clock) private val mUserSettingsCategory by lazy { findPreference(VectorPreferences.SETTINGS_USER_SETTINGS_PREFERENCE_KEY)!! @@ -177,7 +180,7 @@ class VectorSettingsGeneralFragment @Inject constructor( // Password // Hide the preference if password can not be updated - if (session.getHomeServerCapabilities().canChangePassword) { + if (session.homeServerCapabilitiesService().getHomeServerCapabilities().canChangePassword) { mPasswordPreference.onPreferenceClickListener = Preference.OnPreferenceClickListener { onPasswordUpdateClick() false @@ -332,7 +335,7 @@ class VectorSettingsGeneralFragment @Inject constructor( lifecycleScope.launch { val result = runCatching { - session.updateAvatar(session.myUserId, uri, getFilenameFromUri(context, uri) ?: UUID.randomUUID().toString()) + session.profileService().updateAvatar(session.myUserId, uri, getFilenameFromUri(context, uri) ?: UUID.randomUUID().toString()) } if (!isAdded) return@launch @@ -444,7 +447,7 @@ class VectorSettingsGeneralFragment @Inject constructor( showPasswordLoadingView(true) lifecycleScope.launch { val result = runCatching { - session.changePassword(oldPwd, newPwd) + session.accountService().changePassword(oldPwd, newPwd) } if (!isAdded) { return@launch @@ -476,7 +479,7 @@ class VectorSettingsGeneralFragment @Inject constructor( displayLoadingView() lifecycleScope.launch { - val result = runCatching { session.setDisplayName(session.myUserId, value) } + val result = runCatching { session.profileService().setDisplayName(session.myUserId, value) } if (!isAdded) return@launch result.fold( onSuccess = { diff --git a/vector/src/main/java/im/vector/app/features/settings/VectorSettingsHelpAboutFragment.kt b/vector/src/main/java/im/vector/app/features/settings/VectorSettingsHelpAboutFragment.kt index 3a999f299f..5844467a1f 100644 --- a/vector/src/main/java/im/vector/app/features/settings/VectorSettingsHelpAboutFragment.kt +++ b/vector/src/main/java/im/vector/app/features/settings/VectorSettingsHelpAboutFragment.kt @@ -20,6 +20,7 @@ import android.os.Bundle import androidx.preference.Preference import im.vector.app.BuildConfig import im.vector.app.R +import im.vector.app.core.extensions.orEmpty import im.vector.app.core.preference.VectorPreference import im.vector.app.core.utils.FirstThrottler import im.vector.app.core.utils.copyToClipboard @@ -72,7 +73,7 @@ class VectorSettingsHelpAboutFragment @Inject constructor( } it.setOnPreferenceClickListener { pref -> - copyToClipboard(requireContext(), pref.summary) + copyToClipboard(requireContext(), pref.summary.orEmpty()) true } } @@ -82,7 +83,7 @@ class VectorSettingsHelpAboutFragment @Inject constructor( it.summary = Matrix.getSdkVersion() it.setOnPreferenceClickListener { pref -> - copyToClipboard(requireContext(), pref.summary) + copyToClipboard(requireContext(), pref.summary.orEmpty()) true } } diff --git a/vector/src/main/java/im/vector/app/features/settings/VectorSettingsLabsFragment.kt b/vector/src/main/java/im/vector/app/features/settings/VectorSettingsLabsFragment.kt index 0ef8e51429..3b9fdc5e55 100644 --- a/vector/src/main/java/im/vector/app/features/settings/VectorSettingsLabsFragment.kt +++ b/vector/src/main/java/im/vector/app/features/settings/VectorSettingsLabsFragment.kt @@ -17,18 +17,23 @@ package im.vector.app.features.settings import android.os.Bundle +import android.text.method.LinkMovementMethod +import android.widget.TextView import androidx.preference.Preference +import com.google.android.material.dialog.MaterialAlertDialogBuilder import im.vector.app.R import im.vector.app.core.preference.VectorSwitchPreference import im.vector.app.features.MainActivity import im.vector.app.features.MainActivityArgs import im.vector.app.features.analytics.plan.MobileScreen +import im.vector.app.features.home.room.threads.ThreadsManager import org.matrix.android.sdk.api.settings.LightweightSettingsStorage import javax.inject.Inject class VectorSettingsLabsFragment @Inject constructor( private val vectorPreferences: VectorPreferences, - private val lightweightSettingsStorage: LightweightSettingsStorage + private val lightweightSettingsStorage: LightweightSettingsStorage, + private val threadsManager: ThreadsManager ) : VectorSettingsBaseFragment() { override var titleRes = R.string.room_settings_labs_pref_title @@ -46,15 +51,51 @@ class VectorSettingsLabsFragment @Inject constructor( } // clear cache - findPreference(VectorPreferences.SETTINGS_LABS_ENABLE_THREAD_MESSAGES)?.let { - it.onPreferenceClickListener = Preference.OnPreferenceClickListener { - // We should migrate threads only if threads are disabled - vectorPreferences.setShouldMigrateThreads(!vectorPreferences.areThreadMessagesEnabled()) - lightweightSettingsStorage.setThreadMessagesEnabled(vectorPreferences.areThreadMessagesEnabled()) - displayLoadingView() - MainActivity.restartApp(requireActivity(), MainActivityArgs(clearCache = true)) + findPreference(VectorPreferences.SETTINGS_LABS_ENABLE_THREAD_MESSAGES)?.let { vectorPref -> + vectorPref.onPreferenceClickListener = Preference.OnPreferenceClickListener { + onThreadsPreferenceClickedInterceptor(vectorPref) false } } } + + /** + * Intercept the click to display a user friendly dialog when their homeserver do not support threads. + */ + private fun onThreadsPreferenceClickedInterceptor(vectorSwitchPreference: VectorSwitchPreference) { + val userEnabledThreads = vectorPreferences.areThreadMessagesEnabled() + if (!session.homeServerCapabilitiesService().getHomeServerCapabilities().canUseThreading && userEnabledThreads) { + activity?.let { + MaterialAlertDialogBuilder(it) + .setTitle(R.string.threads_labs_enable_notice_title) + .setMessage(threadsManager.getLabsEnableThreadsMessage()) + .setCancelable(true) + .setNegativeButton(R.string.action_not_now) { _, _ -> + vectorSwitchPreference.isChecked = false + } + .setPositiveButton(R.string.action_try_it_out) { _, _ -> + onThreadsPreferenceClicked() + } + .show() + ?.findViewById(android.R.id.message) + ?.apply { + linksClickable = true + movementMethod = LinkMovementMethod.getInstance() + } + } + } else { + onThreadsPreferenceClicked() + } + } + + /** + * Action when threads preference switch is actually clicked. + */ + private fun onThreadsPreferenceClicked() { + // We should migrate threads only if threads are disabled + vectorPreferences.setShouldMigrateThreads(!vectorPreferences.areThreadMessagesEnabled()) + lightweightSettingsStorage.setThreadMessagesEnabled(vectorPreferences.areThreadMessagesEnabled()) + displayLoadingView() + MainActivity.restartApp(requireActivity(), MainActivityArgs(clearCache = true)) + } } diff --git a/vector/src/main/java/im/vector/app/features/settings/VectorSettingsPreferencesFragment.kt b/vector/src/main/java/im/vector/app/features/settings/VectorSettingsPreferencesFragment.kt index 155234a172..5033400425 100644 --- a/vector/src/main/java/im/vector/app/features/settings/VectorSettingsPreferencesFragment.kt +++ b/vector/src/main/java/im/vector/app/features/settings/VectorSettingsPreferencesFragment.kt @@ -86,7 +86,7 @@ class VectorSettingsPreferencesFragment @Inject constructor( pref.setOnPreferenceChangeListener { _, newValue -> val presenceOfflineModeEnabled = newValue as? Boolean ?: false lifecycleScope.launch { - session.setMyPresence(if (presenceOfflineModeEnabled) PresenceEnum.OFFLINE else PresenceEnum.ONLINE) + session.presenceService().setMyPresence(if (presenceOfflineModeEnabled) PresenceEnum.OFFLINE else PresenceEnum.ONLINE) } true } @@ -147,8 +147,10 @@ class VectorSettingsPreferencesFragment @Inject constructor( it.onPreferenceClickListener = Preference.OnPreferenceClickListener { context?.let { context: Context -> MaterialAlertDialogBuilder(context) - .setSingleChoiceItems(R.array.media_saving_choice, - vectorPreferences.getSelectedMediasSavingPeriod()) { d, n -> + .setSingleChoiceItems( + R.array.media_saving_choice, + vectorPreferences.getSelectedMediasSavingPeriod() + ) { d, n -> vectorPreferences.setSelectedMediasSavingPeriod(n) d.cancel() 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 1581bb4676..622f1f3f97 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 @@ -151,7 +151,7 @@ class VectorSettingsSecurityPrivacyFragment @Inject constructor( findPreference("SETTINGS_USER_ANALYTICS_CONSENT_KEY")!! } - override fun onCreateRecyclerView(inflater: LayoutInflater?, parent: ViewGroup?, savedInstanceState: Bundle?): RecyclerView { + override fun onCreateRecyclerView(inflater: LayoutInflater, parent: ViewGroup, savedInstanceState: Bundle?): RecyclerView { return super.onCreateRecyclerView(inflater, parent, savedInstanceState).also { // Insert animation are really annoying the first time the list is shown // due to the way preference fragment is done, it's not trivial to disable it for first appearance only.. @@ -186,6 +186,10 @@ class VectorSettingsSecurityPrivacyFragment @Inject constructor( private val secureBackupPreference by lazy { findPreference("SETTINGS_SECURE_BACKUP_RECOVERY_PREFERENCE_KEY")!! } + + private val ignoredUsersPreference by lazy { + findPreference("SETTINGS_IGNORED_USERS_PREFERENCE_KEY")!! + } // private val secureBackupResetPreference by lazy { // findPreference(VectorPreferences.SETTINGS_SECURE_BACKUP_RESET_PREFERENCE_KEY) // } @@ -271,8 +275,17 @@ class VectorSettingsSecurityPrivacyFragment @Inject constructor( refreshXSigningStatus() secureBackupPreference.icon = activity?.let { - ThemeUtils.tintDrawable(it, - ContextCompat.getDrawable(it, R.drawable.ic_secure_backup)!!, R.attr.vctr_content_primary) + ThemeUtils.tintDrawable( + it, + ContextCompat.getDrawable(it, R.drawable.ic_secure_backup)!!, R.attr.vctr_content_primary + ) + } + + ignoredUsersPreference.icon = activity?.let { + ThemeUtils.tintDrawable( + it, + ContextCompat.getDrawable(it, R.drawable.ic_settings_root_ignored_users)!!, R.attr.vctr_content_primary + ) } findPreference(VectorPreferences.SETTINGS_CRYPTOGRAPHY_HS_ADMIN_DISABLED_E2E_DEFAULT)?.let { @@ -389,7 +402,8 @@ class VectorSettingsSecurityPrivacyFragment @Inject constructor( navigator.openPinCode( requireContext(), pinActivityResultLauncher, - PinMode.AUTH) + PinMode.AUTH + ) } else { doOpenPinCodePreferenceScreen() } @@ -503,10 +517,14 @@ class VectorSettingsSecurityPrivacyFragment @Inject constructor( if (data != null) { MaterialAlertDialogBuilder(thisActivity) - .setMessage(resources.getQuantityString(R.plurals.encryption_import_room_keys_success, - data.successfullyNumberOfImportedKeys, - data.successfullyNumberOfImportedKeys, - data.totalNumberOfKeys)) + .setMessage( + resources.getQuantityString( + R.plurals.encryption_import_room_keys_success, + data.successfullyNumberOfImportedKeys, + data.successfullyNumberOfImportedKeys, + data.totalNumberOfKeys + ) + ) .setPositiveButton(R.string.ok) { dialog, _ -> dialog.dismiss() } .show() } diff --git a/vector/src/main/java/im/vector/app/features/settings/account/deactivation/DeactivateAccountFragment.kt b/vector/src/main/java/im/vector/app/features/settings/account/deactivation/DeactivateAccountFragment.kt index 4397da00c4..56c1fccb06 100644 --- a/vector/src/main/java/im/vector/app/features/settings/account/deactivation/DeactivateAccountFragment.kt +++ b/vector/src/main/java/im/vector/app/features/settings/account/deactivation/DeactivateAccountFragment.kt @@ -34,6 +34,7 @@ import im.vector.app.features.analytics.plan.MobileScreen import im.vector.app.features.auth.ReAuthActivity import im.vector.app.features.settings.VectorSettingsActivity import org.matrix.android.sdk.api.auth.data.LoginFlowTypes +import org.matrix.android.sdk.api.session.uia.exceptions.UiaCancelledException import javax.inject.Inject class DeactivateAccountFragment @Inject constructor() : VectorBaseFragment() { @@ -94,8 +95,10 @@ class DeactivateAccountFragment @Inject constructor() : VectorBaseFragment { settingsActivity?.ignoreInvalidTokenError = false dismissLoadingDialog() - displayErrorDialog(it.throwable) + if (it.throwable !is UiaCancelledException) { + displayErrorDialog(it.throwable) + } } DeactivateAccountViewEvents.Done -> { MainActivity.restartApp(requireActivity(), MainActivityArgs(clearCredentials = true, isAccountDeactivated = true)) } is DeactivateAccountViewEvents.RequestReAuth -> { - ReAuthActivity.newIntent(requireContext(), + ReAuthActivity.newIntent( + requireContext(), it.registrationFlowResponse, it.lastErrorCode, - getString(R.string.deactivate_account_title)).let { intent -> + getString(R.string.deactivate_account_title) + ).let { intent -> reAuthActivityResultLauncher.launch(intent) } } diff --git a/vector/src/main/java/im/vector/app/features/settings/account/deactivation/DeactivateAccountViewEvents.kt b/vector/src/main/java/im/vector/app/features/settings/account/deactivation/DeactivateAccountViewEvents.kt index 1b0ec2de0c..b715de3387 100644 --- a/vector/src/main/java/im/vector/app/features/settings/account/deactivation/DeactivateAccountViewEvents.kt +++ b/vector/src/main/java/im/vector/app/features/settings/account/deactivation/DeactivateAccountViewEvents.kt @@ -20,7 +20,7 @@ import im.vector.app.core.platform.VectorViewEvents import org.matrix.android.sdk.api.auth.registration.RegistrationFlowResponse /** - * Transient events for deactivate account settings screen + * Transient events for deactivate account settings screen. */ sealed class DeactivateAccountViewEvents : VectorViewEvents { data class Loading(val message: CharSequence? = null) : DeactivateAccountViewEvents() diff --git a/vector/src/main/java/im/vector/app/features/settings/account/deactivation/DeactivateAccountViewModel.kt b/vector/src/main/java/im/vector/app/features/settings/account/deactivation/DeactivateAccountViewModel.kt index 95537a8700..ad73854060 100644 --- a/vector/src/main/java/im/vector/app/features/settings/account/deactivation/DeactivateAccountViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/settings/account/deactivation/DeactivateAccountViewModel.kt @@ -32,6 +32,7 @@ import org.matrix.android.sdk.api.auth.registration.RegistrationFlowResponse import org.matrix.android.sdk.api.failure.isInvalidUIAAuth import org.matrix.android.sdk.api.session.Session import org.matrix.android.sdk.api.session.uia.DefaultBaseAuth +import org.matrix.android.sdk.api.session.uia.exceptions.UiaCancelledException import org.matrix.android.sdk.api.util.fromBase64 import timber.log.Timber import kotlin.coroutines.Continuation @@ -44,7 +45,7 @@ data class DeactivateAccountViewState( class DeactivateAccountViewModel @AssistedInject constructor(@Assisted private val initialState: DeactivateAccountViewState, private val session: Session) : - VectorViewModel(initialState) { + VectorViewModel(initialState) { @AssistedFactory interface Factory : MavericksAssistedViewModelFactory { @@ -57,16 +58,19 @@ class DeactivateAccountViewModel @AssistedInject constructor(@Assisted private v override fun handle(action: DeactivateAccountAction) { when (action) { is DeactivateAccountAction.DeactivateAccount -> handleDeactivateAccount(action) - DeactivateAccountAction.SsoAuthDone -> { + DeactivateAccountAction.SsoAuthDone -> { Timber.d("## UIA - FallBack success") + _viewEvents.post(DeactivateAccountViewEvents.Loading()) if (pendingAuth != null) { uiaContinuation?.resume(pendingAuth!!) } else { uiaContinuation?.resumeWithException(IllegalArgumentException()) } } - is DeactivateAccountAction.PasswordAuthDone -> { - val decryptedPass = session.loadSecureSecret(action.password.fromBase64().inputStream(), ReAuthActivity.DEFAULT_RESULT_KEYSTORE_ALIAS) + is DeactivateAccountAction.PasswordAuthDone -> { + _viewEvents.post(DeactivateAccountViewEvents.Loading()) + val decryptedPass = session.secureStorageService() + .loadSecureSecret(action.password.fromBase64().inputStream(), ReAuthActivity.DEFAULT_RESULT_KEYSTORE_ALIAS) uiaContinuation?.resume( UserPasswordAuth( session = pendingAuth?.session, @@ -75,9 +79,9 @@ class DeactivateAccountViewModel @AssistedInject constructor(@Assisted private v ) ) } - DeactivateAccountAction.ReAuthCancelled -> { + DeactivateAccountAction.ReAuthCancelled -> { Timber.d("## UIA - Reauth cancelled") - uiaContinuation?.resumeWithException(Exception()) + uiaContinuation?.resumeWithException(UiaCancelledException()) uiaContinuation = null pendingAuth = null } @@ -89,7 +93,7 @@ class DeactivateAccountViewModel @AssistedInject constructor(@Assisted private v viewModelScope.launch { val event = try { - session.deactivateAccount( + session.accountService().deactivateAccount( action.eraseAllData, object : UserInteractiveAuthInterceptor { override fun performStage(flowResponse: RegistrationFlowResponse, errCode: String?, promise: Continuation) { @@ -100,7 +104,7 @@ class DeactivateAccountViewModel @AssistedInject constructor(@Assisted private v } ) DeactivateAccountViewEvents.Done - } catch (failure: Exception) { + } catch (failure: Throwable) { if (failure.isInvalidUIAAuth()) { DeactivateAccountViewEvents.InvalidAuth } else { diff --git a/vector/src/main/java/im/vector/app/features/settings/crosssigning/CrossSigningSettingsFragment.kt b/vector/src/main/java/im/vector/app/features/settings/crosssigning/CrossSigningSettingsFragment.kt index 51ab422935..f5e87fc1a0 100644 --- a/vector/src/main/java/im/vector/app/features/settings/crosssigning/CrossSigningSettingsFragment.kt +++ b/vector/src/main/java/im/vector/app/features/settings/crosssigning/CrossSigningSettingsFragment.kt @@ -37,7 +37,7 @@ import org.matrix.android.sdk.api.auth.data.LoginFlowTypes import javax.inject.Inject /** - * This Fragment is only used when user activates developer mode from the settings + * This Fragment is only used when user activates developer mode from the settings. */ class CrossSigningSettingsFragment @Inject constructor( private val controller: CrossSigningSettingsController, @@ -85,10 +85,12 @@ class CrossSigningSettingsFragment @Inject constructor( Unit } is CrossSigningSettingsViewEvents.RequestReAuth -> { - ReAuthActivity.newIntent(requireContext(), + ReAuthActivity.newIntent( + requireContext(), event.registrationFlowResponse, event.lastErrorCode, - getString(R.string.initialize_cross_signing)).let { intent -> + getString(R.string.initialize_cross_signing) + ).let { intent -> reAuthActivityResultLauncher.launch(intent) } } diff --git a/vector/src/main/java/im/vector/app/features/settings/crosssigning/CrossSigningSettingsViewEvents.kt b/vector/src/main/java/im/vector/app/features/settings/crosssigning/CrossSigningSettingsViewEvents.kt index 1c11560d40..8a9ba012fe 100644 --- a/vector/src/main/java/im/vector/app/features/settings/crosssigning/CrossSigningSettingsViewEvents.kt +++ b/vector/src/main/java/im/vector/app/features/settings/crosssigning/CrossSigningSettingsViewEvents.kt @@ -20,7 +20,7 @@ import im.vector.app.core.platform.VectorViewEvents import org.matrix.android.sdk.api.auth.registration.RegistrationFlowResponse /** - * Transient events for cross signing settings screen + * Transient events for cross signing settings screen. */ sealed class CrossSigningSettingsViewEvents : VectorViewEvents { data class Failure(val throwable: Throwable) : CrossSigningSettingsViewEvents() diff --git a/vector/src/main/java/im/vector/app/features/settings/crosssigning/CrossSigningSettingsViewModel.kt b/vector/src/main/java/im/vector/app/features/settings/crosssigning/CrossSigningSettingsViewModel.kt index 0d4c39a230..c81064c8d9 100644 --- a/vector/src/main/java/im/vector/app/features/settings/crosssigning/CrossSigningSettingsViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/settings/crosssigning/CrossSigningSettingsViewModel.kt @@ -110,7 +110,8 @@ class CrossSigningSettingsViewModel @AssistedInject constructor( uiaContinuation = promise } } - }, it) + }, it + ) } } catch (failure: Throwable) { handleInitializeXSigningError(failure) @@ -129,7 +130,8 @@ class CrossSigningSettingsViewModel @AssistedInject constructor( } } is CrossSigningSettingsAction.PasswordAuthDone -> { - val decryptedPass = session.loadSecureSecret(action.password.fromBase64().inputStream(), ReAuthActivity.DEFAULT_RESULT_KEYSTORE_ALIAS) + val decryptedPass = session.secureStorageService() + .loadSecureSecret(action.password.fromBase64().inputStream(), ReAuthActivity.DEFAULT_RESULT_KEYSTORE_ALIAS) uiaContinuation?.resume( UserPasswordAuth( session = pendingAuth?.session, diff --git a/vector/src/main/java/im/vector/app/features/settings/devices/DeviceVerificationInfoBottomSheetController.kt b/vector/src/main/java/im/vector/app/features/settings/devices/DeviceVerificationInfoBottomSheetController.kt index 8f04534440..648e9b3261 100644 --- a/vector/src/main/java/im/vector/app/features/settings/devices/DeviceVerificationInfoBottomSheetController.kt +++ b/vector/src/main/java/im/vector/app/features/settings/devices/DeviceVerificationInfoBottomSheetController.kt @@ -99,9 +99,10 @@ class DeviceVerificationInfoBottomSheetController @Inject constructor( style(ItemStyle.BIG_TEXT) titleIconResourceId(shield) title(host.stringProvider.getString(R.string.crosssigning_verify_this_session).toEpoxyCharSequence()) - description(host.stringProvider - .getString(if (data.hasOtherSessions) R.string.confirm_your_identity else R.string.confirm_your_identity_quad_s) - .toEpoxyCharSequence() + description( + host.stringProvider + .getString(if (data.hasOtherSessions) R.string.confirm_your_identity else R.string.confirm_your_identity_quad_s) + .toEpoxyCharSequence() ) } } diff --git a/vector/src/main/java/im/vector/app/features/settings/devices/DeviceVerificationInfoBottomSheetViewModel.kt b/vector/src/main/java/im/vector/app/features/settings/devices/DeviceVerificationInfoBottomSheetViewModel.kt index 2573a2f395..b24b8475a5 100644 --- a/vector/src/main/java/im/vector/app/features/settings/devices/DeviceVerificationInfoBottomSheetViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/settings/devices/DeviceVerificationInfoBottomSheetViewModel.kt @@ -48,7 +48,7 @@ class DeviceVerificationInfoBottomSheetViewModel @AssistedInject constructor(@As copy( hasAccountCrossSigning = session.cryptoService().crossSigningService().isCrossSigningInitialized(), accountCrossSigningIsTrusted = session.cryptoService().crossSigningService().isCrossSigningVerified(), - isRecoverySetup = session.sharedSecretStorageService.isRecoverySetup() + isRecoverySetup = session.sharedSecretStorageService().isRecoverySetup() ) } session.flow().liveCrossSigningInfo(session.myUserId) diff --git a/vector/src/main/java/im/vector/app/features/settings/devices/DevicesAction.kt b/vector/src/main/java/im/vector/app/features/settings/devices/DevicesAction.kt index dd8fca3820..8ee0e7636e 100644 --- a/vector/src/main/java/im/vector/app/features/settings/devices/DevicesAction.kt +++ b/vector/src/main/java/im/vector/app/features/settings/devices/DevicesAction.kt @@ -23,7 +23,7 @@ sealed class DevicesAction : VectorViewModelAction { object Refresh : DevicesAction() data class Delete(val deviceId: String) : DevicesAction() -// data class Password(val password: String) : DevicesAction() + // data class Password(val password: String) : DevicesAction() data class Rename(val deviceId: String, val newName: String) : DevicesAction() data class PromptRename(val deviceId: String) : DevicesAction() diff --git a/vector/src/main/java/im/vector/app/features/settings/devices/DevicesViewEvents.kt b/vector/src/main/java/im/vector/app/features/settings/devices/DevicesViewEvents.kt index 941b33a8ef..c057e2b565 100644 --- a/vector/src/main/java/im/vector/app/features/settings/devices/DevicesViewEvents.kt +++ b/vector/src/main/java/im/vector/app/features/settings/devices/DevicesViewEvents.kt @@ -24,12 +24,12 @@ import org.matrix.android.sdk.api.session.crypto.model.CryptoDeviceInfo import org.matrix.android.sdk.api.session.crypto.model.DeviceInfo /** - * Transient events for Ignored users screen + * Transient events for Ignored users screen. */ sealed class DevicesViewEvents : VectorViewEvents { data class Loading(val message: CharSequence? = null) : DevicesViewEvents() -// object HideLoading : DevicesViewEvents() + // object HideLoading : DevicesViewEvents() data class Failure(val throwable: Throwable) : DevicesViewEvents() // object RequestPassword : DevicesViewEvents() diff --git a/vector/src/main/java/im/vector/app/features/settings/devices/DevicesViewModel.kt b/vector/src/main/java/im/vector/app/features/settings/devices/DevicesViewModel.kt index 46a2ba03b1..e56e08d19e 100644 --- a/vector/src/main/java/im/vector/app/features/settings/devices/DevicesViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/settings/devices/DevicesViewModel.kt @@ -219,7 +219,8 @@ class DevicesViewModel @AssistedInject constructor( Unit } is DevicesAction.PasswordAuthDone -> { - val decryptedPass = session.loadSecureSecret(action.password.fromBase64().inputStream(), ReAuthActivity.DEFAULT_RESULT_KEYSTORE_ALIAS) + val decryptedPass = session.secureStorageService() + .loadSecureSecret(action.password.fromBase64().inputStream(), ReAuthActivity.DEFAULT_RESULT_KEYSTORE_ALIAS) uiaContinuation?.resume( UserPasswordAuth( session = pendingAuth?.session, @@ -243,10 +244,12 @@ class DevicesViewModel @AssistedInject constructor( val txID = session.cryptoService() .verificationService() .beginKeyVerification(VerificationMethod.SAS, session.myUserId, action.deviceId, null) - _viewEvents.post(DevicesViewEvents.ShowVerifyDevice( - session.myUserId, - txID - )) + _viewEvents.post( + DevicesViewEvents.ShowVerifyDevice( + session.myUserId, + txID + ) + ) } private fun handleShowDeviceCryptoInfo(action: DevicesAction.VerifyMyDeviceManually) = withState { state -> @@ -273,7 +276,8 @@ class DevicesViewModel @AssistedInject constructor( session.cryptoService().setDeviceVerification( DeviceTrustLevel(crossSigningVerified = false, locallyVerified = true), action.cryptoDeviceInfo.userId, - action.cryptoDeviceInfo.deviceId) + action.cryptoDeviceInfo.deviceId + ) } } } diff --git a/vector/src/main/java/im/vector/app/features/settings/devices/VectorSettingsDevicesFragment.kt b/vector/src/main/java/im/vector/app/features/settings/devices/VectorSettingsDevicesFragment.kt index e897cdccac..ed424e7267 100644 --- a/vector/src/main/java/im/vector/app/features/settings/devices/VectorSettingsDevicesFragment.kt +++ b/vector/src/main/java/im/vector/app/features/settings/devices/VectorSettingsDevicesFragment.kt @@ -43,7 +43,7 @@ import org.matrix.android.sdk.api.session.crypto.model.DeviceInfo import javax.inject.Inject /** - * Display the list of the user's device + * Display the list of the user's device. */ class VectorSettingsDevicesFragment @Inject constructor( private val devicesController: DevicesController @@ -117,7 +117,7 @@ class VectorSettingsDevicesFragment @Inject constructor( } /** - * Display an alert dialog to rename a device + * Display an alert dialog to rename a device. * * @param deviceInfo device info */ @@ -159,13 +159,15 @@ class VectorSettingsDevicesFragment @Inject constructor( } /** - * Launch the re auth activity to get credentials + * Launch the re auth activity to get credentials. */ private fun askForReAuthentication(reAuthReq: DevicesViewEvents.RequestReAuth) { - ReAuthActivity.newIntent(requireContext(), + ReAuthActivity.newIntent( + requireContext(), reAuthReq.registrationFlowResponse, reAuthReq.lastErrorCode, - getString(R.string.devices_delete_dialog_title)).let { intent -> + getString(R.string.devices_delete_dialog_title) + ).let { intent -> reAuthActivityResultLauncher.launch(intent) } } diff --git a/vector/src/main/java/im/vector/app/features/settings/devtools/AccountDataViewModel.kt b/vector/src/main/java/im/vector/app/features/settings/devtools/AccountDataViewModel.kt index 9576b84e98..72085d37fd 100644 --- a/vector/src/main/java/im/vector/app/features/settings/devtools/AccountDataViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/settings/devtools/AccountDataViewModel.kt @@ -38,7 +38,7 @@ data class AccountDataViewState( class AccountDataViewModel @AssistedInject constructor(@Assisted initialState: AccountDataViewState, private val session: Session) : - VectorViewModel(initialState) { + VectorViewModel(initialState) { init { session.flow().liveUserAccountData(emptySet()) diff --git a/vector/src/main/java/im/vector/app/features/settings/devtools/GossipingEventsPaperTrailFragment.kt b/vector/src/main/java/im/vector/app/features/settings/devtools/GossipingEventsPaperTrailFragment.kt index 83740c5018..ec4ef26001 100644 --- a/vector/src/main/java/im/vector/app/features/settings/devtools/GossipingEventsPaperTrailFragment.kt +++ b/vector/src/main/java/im/vector/app/features/settings/devtools/GossipingEventsPaperTrailFragment.kt @@ -27,10 +27,8 @@ import im.vector.app.core.extensions.cleanup import im.vector.app.core.extensions.configureWith import im.vector.app.core.platform.VectorBaseFragment import im.vector.app.core.resources.ColorProvider -import im.vector.app.core.utils.createJSonViewerStyleProvider import im.vector.app.databinding.FragmentGenericRecyclerBinding -import org.billcarsonfr.jsonviewer.JSonViewerDialog -import org.matrix.android.sdk.api.session.events.model.Event +import org.matrix.android.sdk.api.session.crypto.model.AuditTrail import javax.inject.Inject class GossipingEventsPaperTrailFragment @Inject constructor( @@ -64,17 +62,17 @@ class GossipingEventsPaperTrailFragment @Inject constructor( super.onDestroyView() } - override fun didTap(event: Event) { - if (event.isEncrypted()) { - event.toClearContentStringWithIndent() - } else { - event.toContentStringWithIndent() - }?.let { - JSonViewerDialog.newInstance( - it, - -1, - createJSonViewerStyleProvider(colorProvider) - ).show(childFragmentManager, "JSON_VIEWER") - } + override fun didTap(event: AuditTrail) { +// if (event.isEncrypted()) { +// event.toClearContentStringWithIndent() +// } else { +// event.toContentStringWithIndent() +// }?.let { +// JSonViewerDialog.newInstance( +// it, +// -1, +// createJSonViewerStyleProvider(colorProvider) +// ).show(childFragmentManager, "JSON_VIEWER") +// } } } diff --git a/vector/src/main/java/im/vector/app/features/settings/devtools/GossipingEventsPaperTrailViewModel.kt b/vector/src/main/java/im/vector/app/features/settings/devtools/GossipingEventsPaperTrailViewModel.kt index dde032d303..e7f09b09e0 100644 --- a/vector/src/main/java/im/vector/app/features/settings/devtools/GossipingEventsPaperTrailViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/settings/devtools/GossipingEventsPaperTrailViewModel.kt @@ -32,15 +32,15 @@ import im.vector.app.core.platform.EmptyAction import im.vector.app.core.platform.EmptyViewEvents import im.vector.app.core.platform.VectorViewModel import org.matrix.android.sdk.api.session.Session -import org.matrix.android.sdk.api.session.events.model.Event +import org.matrix.android.sdk.api.session.crypto.model.AuditTrail data class GossipingEventsPaperTrailState( - val events: Async> = Uninitialized + val events: Async> = Uninitialized ) : MavericksState class GossipingEventsPaperTrailViewModel @AssistedInject constructor(@Assisted initialState: GossipingEventsPaperTrailState, private val session: Session) : - VectorViewModel(initialState) { + VectorViewModel(initialState) { init { refresh() diff --git a/vector/src/main/java/im/vector/app/features/settings/devtools/GossipingEventsSerializer.kt b/vector/src/main/java/im/vector/app/features/settings/devtools/GossipingEventsSerializer.kt index 30c2efc3ce..b8d82699e6 100644 --- a/vector/src/main/java/im/vector/app/features/settings/devtools/GossipingEventsSerializer.kt +++ b/vector/src/main/java/im/vector/app/features/settings/devtools/GossipingEventsSerializer.kt @@ -17,62 +17,43 @@ package im.vector.app.features.settings.devtools import im.vector.app.core.resources.DateProvider -import me.gujun.android.span.span -import org.matrix.android.sdk.api.session.crypto.model.ForwardedRoomKeyContent -import org.matrix.android.sdk.api.session.crypto.model.GossipingToDeviceObject -import org.matrix.android.sdk.api.session.crypto.model.RoomKeyShareRequest -import org.matrix.android.sdk.api.session.crypto.model.SecretShareRequest -import org.matrix.android.sdk.api.session.events.model.Event -import org.matrix.android.sdk.api.session.events.model.EventType -import org.matrix.android.sdk.api.session.events.model.content.OlmEventContent -import org.matrix.android.sdk.api.session.events.model.content.SecretSendEventContent -import org.matrix.android.sdk.api.session.events.model.toModel +import org.matrix.android.sdk.api.session.crypto.model.AuditTrail +import org.matrix.android.sdk.api.session.crypto.model.ForwardInfo +import org.matrix.android.sdk.api.session.crypto.model.TrailType +import org.matrix.android.sdk.api.session.crypto.model.WithheldInfo import org.threeten.bp.format.DateTimeFormatter class GossipingEventsSerializer { private val full24DateFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss.SSS") - fun serialize(eventList: List): String { + fun serialize(eventList: List): String { return buildString { - eventList.forEach { - val clearType = it.getClearType() - append("[${getFormattedDate(it.ageLocalTs)}] $clearType from:${it.senderId} - ") - when (clearType) { - EventType.ROOM_KEY_REQUEST -> { - val content = it.getClearContent().toModel() - append("reqId:${content?.requestId} action:${content?.action} ") - if (content?.action == GossipingToDeviceObject.ACTION_SHARE_REQUEST) { - append("sessionId: ${content.body?.sessionId} ") - } - append("requestedBy: ${content?.requestingDeviceId}") + eventList.forEach { trail -> + val type = trail.type + val info = trail.info + append("[${getFormattedDate(trail.ageLocalTs)}] ${type.name} ") + append("sessionId: ${info.sessionId} ") + when (type) { + TrailType.IncomingKeyRequest -> { + append("from:${info.userId}|${info.deviceId} - ") } - EventType.FORWARDED_ROOM_KEY -> { - val encryptedContent = it.content.toModel() - val content = it.getClearContent().toModel() - - append("sessionId:${content?.sessionId} From Device (sender key):${encryptedContent?.senderKey}") - span("\nFrom Device (sender key):") { - textStyle = "bold" + TrailType.OutgoingKeyForward -> { + append("to:${info.userId}|${info.deviceId} - ") + (trail.info as? ForwardInfo)?.let { + append("chainIndex: ${it.chainIndex} ") } } - EventType.ROOM_KEY -> { - val content = it.getClearContent() - append("sessionId:${content?.get("session_id")} roomId:${content?.get("room_id")} dest:${content?.get("_dest") ?: "me"}") - } - EventType.SEND_SECRET -> { - val content = it.getClearContent().toModel() - append("requestId:${content?.requestId} From Device:${it.mxDecryptionResult?.payload?.get("sender_device")}") - } - EventType.REQUEST_SECRET -> { - val content = it.getClearContent().toModel() - append("reqId:${content?.requestId} action:${content?.action} ") - if (content?.action == GossipingToDeviceObject.ACTION_SHARE_REQUEST) { - append("secretName:${content.secretName} ") + TrailType.OutgoingKeyWithheld -> { + append("to:${info.userId}|${info.deviceId} - ") + (trail.info as? WithheldInfo)?.let { + append("code: ${it.code} ") } - append("requestedBy:${content?.requestingDeviceId}") } - EventType.ENCRYPTED -> { - append("Failed to Decrypt") + TrailType.IncomingKeyForward -> { + append("from:${info.userId}|${info.deviceId} - ") + (trail.info as? ForwardInfo)?.let { + append("chainIndex: ${it.chainIndex} ") + } } else -> { append("??") diff --git a/vector/src/main/java/im/vector/app/features/settings/devtools/GossipingTrailPagedEpoxyController.kt b/vector/src/main/java/im/vector/app/features/settings/devtools/GossipingTrailPagedEpoxyController.kt index dd016c2bf5..6a45968916 100644 --- a/vector/src/main/java/im/vector/app/features/settings/devtools/GossipingTrailPagedEpoxyController.kt +++ b/vector/src/main/java/im/vector/app/features/settings/devtools/GossipingTrailPagedEpoxyController.kt @@ -18,7 +18,6 @@ package im.vector.app.features.settings.devtools import com.airbnb.epoxy.EpoxyModel import com.airbnb.epoxy.paging.PagedListEpoxyController -import im.vector.app.R import im.vector.app.core.date.DateFormatKind import im.vector.app.core.date.VectorDateFormatter import im.vector.app.core.resources.ColorProvider @@ -26,137 +25,75 @@ import im.vector.app.core.ui.list.GenericItem_ import im.vector.app.core.utils.createUIHandler import im.vector.lib.core.utils.epoxy.charsequence.toEpoxyCharSequence import me.gujun.android.span.span -import org.matrix.android.sdk.api.session.crypto.model.ForwardedRoomKeyContent -import org.matrix.android.sdk.api.session.crypto.model.GossipingToDeviceObject -import org.matrix.android.sdk.api.session.crypto.model.RoomKeyShareRequest -import org.matrix.android.sdk.api.session.crypto.model.SecretShareRequest -import org.matrix.android.sdk.api.session.events.model.Event -import org.matrix.android.sdk.api.session.events.model.EventType -import org.matrix.android.sdk.api.session.events.model.content.OlmEventContent -import org.matrix.android.sdk.api.session.events.model.content.SecretSendEventContent -import org.matrix.android.sdk.api.session.events.model.toModel +import org.matrix.android.sdk.api.session.crypto.model.AuditTrail +import org.matrix.android.sdk.api.session.crypto.model.ForwardInfo +import org.matrix.android.sdk.api.session.crypto.model.TrailType +import org.matrix.android.sdk.api.session.crypto.model.WithheldInfo import javax.inject.Inject class GossipingTrailPagedEpoxyController @Inject constructor( private val vectorDateFormatter: VectorDateFormatter, private val colorProvider: ColorProvider -) : PagedListEpoxyController( +) : PagedListEpoxyController( // Important it must match the PageList builder notify Looper modelBuildingHandler = createUIHandler() ) { interface InteractionListener { - fun didTap(event: Event) + fun didTap(event: AuditTrail) } var interactionListener: InteractionListener? = null - override fun buildItemModel(currentPosition: Int, item: Event?): EpoxyModel<*> { + override fun buildItemModel(currentPosition: Int, item: AuditTrail?): EpoxyModel<*> { val host = this val event = item ?: return GenericItem_().apply { id(currentPosition) } return GenericItem_().apply { id(event.hashCode()) itemClickAction { host.interactionListener?.didTap(event) } - title( - if (event.isEncrypted()) { - "${event.getClearType()} [encrypted]" - } else { - event.type - }?.toEpoxyCharSequence() - ) + title(event.type.name.toEpoxyCharSequence()) description( span { +host.vectorDateFormatter.format(event.ageLocalTs, DateFormatKind.DEFAULT_DATE_AND_TIME) - span("\nfrom: ") { + span("\n${host.senderFromTo(event.type)}: ") { textStyle = "bold" } - +"${event.senderId}" + +"${event.info.userId}|${event.info.deviceId}" + span("\nroomId: ") { + textStyle = "bold" + } + +event.info.roomId + span("\nsessionId: ") { + textStyle = "bold" + } + +event.info.sessionId apply { - if (event.getClearType() == EventType.ROOM_KEY_REQUEST) { - val content = event.getClearContent().toModel() - span("\nreqId:") { - textStyle = "bold" - } - +" ${content?.requestId}" - span("\naction:") { - textStyle = "bold" - } - +" ${content?.action}" - if (content?.action == GossipingToDeviceObject.ACTION_SHARE_REQUEST) { - span("\nsessionId:") { + when (event.type) { + TrailType.OutgoingKeyForward -> { + val fInfo = event.info as ForwardInfo + span("\nchainIndex: ") { textStyle = "bold" } - +" ${content.body?.sessionId}" + +"${fInfo.chainIndex}" } - span("\nrequestedBy: ") { - textStyle = "bold" - } - +"${content?.requestingDeviceId}" - } else if (event.getClearType() == EventType.FORWARDED_ROOM_KEY) { - val encryptedContent = event.content.toModel() - val content = event.getClearContent().toModel() - if (event.mxDecryptionResult == null) { - span("**Failed to Decrypt** ${event.mCryptoError}") { - textColor = host.colorProvider.getColorFromAttribute(R.attr.colorError) - } - } - span("\nsessionId:") { - textStyle = "bold" - } - +" ${content?.sessionId}" - span("\nFrom Device (sender key):") { - textStyle = "bold" - } - +" ${encryptedContent?.senderKey}" - } else if (event.getClearType() == EventType.ROOM_KEY) { - // it's a bit of a fake event for trail reasons - val content = event.getClearContent() - span("\nsessionId:") { - textStyle = "bold" - } - +" ${content?.get("session_id")}" - span("\nroomId:") { - textStyle = "bold" - } - +" ${content?.get("room_id")}" - span("\nTo :") { - textStyle = "bold" - } - +" ${content?.get("_dest") ?: "me"}" - } else if (event.getClearType() == EventType.SEND_SECRET) { - val content = event.getClearContent().toModel() - - span("\nrequestId:") { - textStyle = "bold" - } - +" ${content?.requestId}" - span("\nFrom Device:") { - textStyle = "bold" - } - +" ${event.mxDecryptionResult?.payload?.get("sender_device")}" - } else if (event.getClearType() == EventType.REQUEST_SECRET) { - val content = event.getClearContent().toModel() - span("\nreqId:") { - textStyle = "bold" - } - +" ${content?.requestId}" - span("\naction:") { - textStyle = "bold" - } - +" ${content?.action}" - if (content?.action == GossipingToDeviceObject.ACTION_SHARE_REQUEST) { - span("\nsecretName:") { + TrailType.OutgoingKeyWithheld -> { + val fInfo = event.info as WithheldInfo + span("\ncode: ") { textStyle = "bold" } - +" ${content.secretName}" + +"${fInfo.code}" } - span("\nrequestedBy: ") { - textStyle = "bold" + TrailType.IncomingKeyRequest -> { + // no additional info } - +"${content?.requestingDeviceId}" - } else if (event.getClearType() == EventType.ENCRYPTED) { - span("**Failed to Decrypt** ${event.mCryptoError}") { - textColor = host.colorProvider.getColorFromAttribute(R.attr.colorError) + TrailType.IncomingKeyForward -> { + val fInfo = event.info as ForwardInfo + span("\nchainIndex: ") { + textStyle = "bold" + } + +"${fInfo.chainIndex}" + } + TrailType.Unknown -> { } } } @@ -164,4 +101,14 @@ class GossipingTrailPagedEpoxyController @Inject constructor( ) } } + + private fun senderFromTo(type: TrailType): String { + return when (type) { + TrailType.OutgoingKeyWithheld, + TrailType.OutgoingKeyForward -> "to" + TrailType.IncomingKeyRequest, + TrailType.IncomingKeyForward -> "from" + TrailType.Unknown -> "" + } + } } diff --git a/vector/src/main/java/im/vector/app/features/settings/devtools/IncomingKeyRequestPagedController.kt b/vector/src/main/java/im/vector/app/features/settings/devtools/IncomingKeyRequestPagedController.kt index 4653f04f2c..09dd198bee 100644 --- a/vector/src/main/java/im/vector/app/features/settings/devtools/IncomingKeyRequestPagedController.kt +++ b/vector/src/main/java/im/vector/app/features/settings/devtools/IncomingKeyRequestPagedController.kt @@ -53,6 +53,7 @@ class IncomingKeyRequestPagedController @Inject constructor( textStyle = "bold" } span("${roomKeyRequest.userId}") + +"\n" +host.vectorDateFormatter.format(roomKeyRequest.localCreationTimestamp, DateFormatKind.DEFAULT_DATE_AND_TIME) span("\nsessionId:") { textStyle = "bold" @@ -62,10 +63,6 @@ class IncomingKeyRequestPagedController @Inject constructor( textStyle = "bold" } +"${roomKeyRequest.deviceId}" - span("\nstate: ") { - textStyle = "bold" - } - +roomKeyRequest.state.name }.toEpoxyCharSequence() ) } diff --git a/vector/src/main/java/im/vector/app/features/settings/devtools/KeyRequestListViewModel.kt b/vector/src/main/java/im/vector/app/features/settings/devtools/KeyRequestListViewModel.kt index a8045c07e3..e0a130a1e6 100644 --- a/vector/src/main/java/im/vector/app/features/settings/devtools/KeyRequestListViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/settings/devtools/KeyRequestListViewModel.kt @@ -32,12 +32,12 @@ import im.vector.app.core.platform.EmptyViewEvents import im.vector.app.core.platform.VectorViewModel import kotlinx.coroutines.launch import org.matrix.android.sdk.api.session.Session +import org.matrix.android.sdk.api.session.crypto.OutgoingKeyRequest import org.matrix.android.sdk.api.session.crypto.model.IncomingRoomKeyRequest -import org.matrix.android.sdk.api.session.crypto.model.OutgoingRoomKeyRequest data class KeyRequestListViewState( val incomingRequests: Async> = Uninitialized, - val outgoingRoomKeyRequests: Async> = Uninitialized + val outgoingRoomKeyRequests: Async> = Uninitialized ) : MavericksState class KeyRequestListViewModel @AssistedInject constructor(@Assisted initialState: KeyRequestListViewState, diff --git a/vector/src/main/java/im/vector/app/features/settings/devtools/KeyRequestViewModel.kt b/vector/src/main/java/im/vector/app/features/settings/devtools/KeyRequestViewModel.kt index fd1cd3480d..f2df0e4211 100644 --- a/vector/src/main/java/im/vector/app/features/settings/devtools/KeyRequestViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/settings/devtools/KeyRequestViewModel.kt @@ -51,7 +51,7 @@ data class KeyRequestViewState( class KeyRequestViewModel @AssistedInject constructor( @Assisted initialState: KeyRequestViewState, private val session: Session) : - VectorViewModel(initialState) { + VectorViewModel(initialState) { @AssistedFactory interface Factory : MavericksAssistedViewModelFactory { diff --git a/vector/src/main/java/im/vector/app/features/settings/devtools/KeyRequestsFragment.kt b/vector/src/main/java/im/vector/app/features/settings/devtools/KeyRequestsFragment.kt index cef68c01c1..4225cbdbec 100644 --- a/vector/src/main/java/im/vector/app/features/settings/devtools/KeyRequestsFragment.kt +++ b/vector/src/main/java/im/vector/app/features/settings/devtools/KeyRequestsFragment.kt @@ -36,12 +36,15 @@ import im.vector.app.R import im.vector.app.core.extensions.registerStartForActivityResult import im.vector.app.core.extensions.safeOpenOutputStream import im.vector.app.core.platform.VectorBaseFragment +import im.vector.app.core.time.Clock import im.vector.app.core.utils.selectTxtFileToWrite import im.vector.app.databinding.FragmentDevtoolKeyrequestsBinding import org.matrix.android.sdk.api.extensions.tryOrNull import javax.inject.Inject -class KeyRequestsFragment @Inject constructor() : VectorBaseFragment() { +class KeyRequestsFragment @Inject constructor( + private val clock: Clock, +) : VectorBaseFragment() { override fun getBinding(inflater: LayoutInflater, container: ViewGroup?): FragmentDevtoolKeyrequestsBinding { return FragmentDevtoolKeyrequestsBinding.inflate(inflater, container, false) @@ -126,7 +129,7 @@ class KeyRequestsFragment @Inject constructor() : VectorBaseFragment() { diff --git a/vector/src/main/java/im/vector/app/features/settings/devtools/OutgoingKeyRequestPagedController.kt b/vector/src/main/java/im/vector/app/features/settings/devtools/OutgoingKeyRequestPagedController.kt index b23bd77277..13ba4a69ac 100644 --- a/vector/src/main/java/im/vector/app/features/settings/devtools/OutgoingKeyRequestPagedController.kt +++ b/vector/src/main/java/im/vector/app/features/settings/devtools/OutgoingKeyRequestPagedController.kt @@ -22,10 +22,10 @@ import im.vector.app.core.ui.list.GenericItem_ import im.vector.app.core.utils.createUIHandler import im.vector.lib.core.utils.epoxy.charsequence.toEpoxyCharSequence import me.gujun.android.span.span -import org.matrix.android.sdk.api.session.crypto.model.OutgoingRoomKeyRequest +import org.matrix.android.sdk.api.session.crypto.OutgoingKeyRequest import javax.inject.Inject -class OutgoingKeyRequestPagedController @Inject constructor() : PagedListEpoxyController( +class OutgoingKeyRequestPagedController @Inject constructor() : PagedListEpoxyController( // Important it must match the PageList builder notify Looper modelBuildingHandler = createUIHandler() ) { @@ -36,7 +36,7 @@ class OutgoingKeyRequestPagedController @Inject constructor() : PagedListEpoxyCo var interactionListener: InteractionListener? = null - override fun buildItemModel(currentPosition: Int, item: OutgoingRoomKeyRequest?): EpoxyModel<*> { + override fun buildItemModel(currentPosition: Int, item: OutgoingKeyRequest?): EpoxyModel<*> { val roomKeyRequest = item ?: return GenericItem_().apply { id(currentPosition) } return GenericItem_().apply { diff --git a/vector/src/main/java/im/vector/app/features/settings/homeserver/HomeserverSettingsFragment.kt b/vector/src/main/java/im/vector/app/features/settings/homeserver/HomeserverSettingsFragment.kt index 28bce90424..09fd848c99 100644 --- a/vector/src/main/java/im/vector/app/features/settings/homeserver/HomeserverSettingsFragment.kt +++ b/vector/src/main/java/im/vector/app/features/settings/homeserver/HomeserverSettingsFragment.kt @@ -31,7 +31,7 @@ import im.vector.app.databinding.FragmentGenericRecyclerBinding import javax.inject.Inject /** - * Display some information about the homeserver + * Display some information about the homeserver. */ class HomeserverSettingsFragment @Inject constructor( private val homeserverSettingsController: HomeserverSettingsController diff --git a/vector/src/main/java/im/vector/app/features/settings/homeserver/HomeserverSettingsViewModel.kt b/vector/src/main/java/im/vector/app/features/settings/homeserver/HomeserverSettingsViewModel.kt index fab563b49e..9e237f818b 100644 --- a/vector/src/main/java/im/vector/app/features/settings/homeserver/HomeserverSettingsViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/settings/homeserver/HomeserverSettingsViewModel.kt @@ -47,7 +47,7 @@ class HomeserverSettingsViewModel @AssistedInject constructor( copy( homeserverUrl = session.sessionParams.homeServerUrl, homeserverClientServerApiUrl = session.sessionParams.homeServerUrlBase, - homeServerCapabilities = session.getHomeServerCapabilities() + homeServerCapabilities = session.homeServerCapabilitiesService().getHomeServerCapabilities() ) } fetchHomeserverVersion() @@ -57,12 +57,12 @@ class HomeserverSettingsViewModel @AssistedInject constructor( private fun refreshHomeServerCapabilities() { viewModelScope.launch { runCatching { - session.refreshHomeServerCapabilities() + session.homeServerCapabilitiesService().refreshHomeServerCapabilities() } setState { copy( - homeServerCapabilities = session.getHomeServerCapabilities() + homeServerCapabilities = session.homeServerCapabilitiesService().getHomeServerCapabilities() ) } } diff --git a/vector/src/main/java/im/vector/app/features/settings/ignored/IgnoredUsersViewEvents.kt b/vector/src/main/java/im/vector/app/features/settings/ignored/IgnoredUsersViewEvents.kt index 8d597a9189..57f5004d88 100644 --- a/vector/src/main/java/im/vector/app/features/settings/ignored/IgnoredUsersViewEvents.kt +++ b/vector/src/main/java/im/vector/app/features/settings/ignored/IgnoredUsersViewEvents.kt @@ -20,7 +20,7 @@ package im.vector.app.features.settings.ignored import im.vector.app.core.platform.VectorViewEvents /** - * Transient events for Ignored users screen + * Transient events for Ignored users screen. */ sealed class IgnoredUsersViewEvents : VectorViewEvents { data class Loading(val message: CharSequence? = null) : IgnoredUsersViewEvents() diff --git a/vector/src/main/java/im/vector/app/features/settings/ignored/IgnoredUsersViewModel.kt b/vector/src/main/java/im/vector/app/features/settings/ignored/IgnoredUsersViewModel.kt index 14f7cd9230..df76d68c17 100644 --- a/vector/src/main/java/im/vector/app/features/settings/ignored/IgnoredUsersViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/settings/ignored/IgnoredUsersViewModel.kt @@ -63,7 +63,7 @@ class IgnoredUsersViewModel @AssistedInject constructor( setState { copy(isLoading = true) } viewModelScope.launch { val viewEvent = try { - session.unIgnoreUserIds(listOf(action.userId)) + session.userService().unIgnoreUserIds(listOf(action.userId)) IgnoredUsersViewEvents.Success } catch (throwable: Throwable) { IgnoredUsersViewEvents.Failure(throwable) diff --git a/vector/src/main/java/im/vector/app/features/settings/ignored/VectorSettingsIgnoredUsersFragment.kt b/vector/src/main/java/im/vector/app/features/settings/ignored/VectorSettingsIgnoredUsersFragment.kt index 66fa690b82..3cb1e29016 100644 --- a/vector/src/main/java/im/vector/app/features/settings/ignored/VectorSettingsIgnoredUsersFragment.kt +++ b/vector/src/main/java/im/vector/app/features/settings/ignored/VectorSettingsIgnoredUsersFragment.kt @@ -30,8 +30,6 @@ import im.vector.app.core.extensions.cleanup import im.vector.app.core.extensions.configureWith import im.vector.app.core.platform.VectorBaseFragment import im.vector.app.databinding.FragmentGenericRecyclerBinding -import im.vector.app.features.MainActivity -import im.vector.app.features.MainActivityArgs import im.vector.app.features.analytics.plan.MobileScreen import javax.inject.Inject @@ -62,16 +60,11 @@ class VectorSettingsIgnoredUsersFragment @Inject constructor( when (it) { is IgnoredUsersViewEvents.Loading -> showLoading(it.message) is IgnoredUsersViewEvents.Failure -> showFailure(it.throwable) - IgnoredUsersViewEvents.Success -> handleSuccess() + IgnoredUsersViewEvents.Success -> Unit } } } - private fun handleSuccess() { - // A user has been un-ignored, perform a initial sync - MainActivity.restartApp(requireActivity(), MainActivityArgs(clearCache = true)) - } - override fun onDestroyView() { ignoredUsersController.callback = null views.genericRecyclerView.cleanup() diff --git a/vector/src/main/java/im/vector/app/features/settings/legals/ElementLegals.kt b/vector/src/main/java/im/vector/app/features/settings/legals/ElementLegals.kt index de59f36604..9f01dea4c1 100644 --- a/vector/src/main/java/im/vector/app/features/settings/legals/ElementLegals.kt +++ b/vector/src/main/java/im/vector/app/features/settings/legals/ElementLegals.kt @@ -26,7 +26,7 @@ class ElementLegals @Inject constructor( private val stringProvider: StringProvider ) { /** - * Use ServerPolicy model + * Use ServerPolicy model. */ fun getData(): List { return listOf( diff --git a/vector/src/main/java/im/vector/app/features/settings/locale/SystemLocaleProvider.kt b/vector/src/main/java/im/vector/app/features/settings/locale/SystemLocaleProvider.kt index 03d3b0623a..43d70ec8cf 100644 --- a/vector/src/main/java/im/vector/app/features/settings/locale/SystemLocaleProvider.kt +++ b/vector/src/main/java/im/vector/app/features/settings/locale/SystemLocaleProvider.kt @@ -26,7 +26,7 @@ class SystemLocaleProvider @Inject constructor( ) { /** - * Provides the device locale + * Provides the device locale. * * @return the device locale, or null in case of error */ diff --git a/vector/src/main/java/im/vector/app/features/settings/notifications/NotificationIndex.kt b/vector/src/main/java/im/vector/app/features/settings/notifications/NotificationIndex.kt index 793b94db1d..a768e443bb 100644 --- a/vector/src/main/java/im/vector/app/features/settings/notifications/NotificationIndex.kt +++ b/vector/src/main/java/im/vector/app/features/settings/notifications/NotificationIndex.kt @@ -16,8 +16,8 @@ package im.vector.app.features.settings.notifications -import org.matrix.android.sdk.api.pushrules.rest.PushRule -import org.matrix.android.sdk.api.pushrules.toJson +import org.matrix.android.sdk.api.session.pushrules.rest.PushRule +import org.matrix.android.sdk.api.session.pushrules.toJson enum class NotificationIndex { OFF, @@ -29,15 +29,16 @@ enum class NotificationIndex { * Given a push rule determine the NotificationIndex by comparing it to the static push rule definitions. * Used when determining the selected state of the PushRulePreference. */ -val PushRule.notificationIndex: NotificationIndex? get() = - NotificationIndex.values().firstOrNull { - // Get the actions for the index - val standardAction = getStandardAction(this.ruleId, it) ?: return@firstOrNull false - val indexActions = standardAction.actions ?: listOf() - // Check if the input rule matches a rule generated from the static rule definitions - val targetRule = this.copy(enabled = standardAction != StandardActions.Disabled, actions = indexActions.toJson()) - ruleMatches(this, targetRule) - } +val PushRule.notificationIndex: NotificationIndex? + get() = + NotificationIndex.values().firstOrNull { + // Get the actions for the index + val standardAction = getStandardAction(this.ruleId, it) ?: return@firstOrNull false + val indexActions = standardAction.actions ?: listOf() + // Check if the input rule matches a rule generated from the static rule definitions + val targetRule = this.copy(enabled = standardAction != StandardActions.Disabled, actions = indexActions.toJson()) + ruleMatches(this, targetRule) + } /** * A check to determine if two push rules should be considered a match. @@ -46,9 +47,9 @@ private fun ruleMatches(rule: PushRule, targetRule: PushRule): Boolean { // Rules match if both are disabled, or if both are enabled and their highlight/sound/notify actions match up. return (!rule.enabled && !targetRule.enabled) || (rule.enabled && - targetRule.enabled && - rule.getHighlight() == targetRule.getHighlight() && - rule.getNotificationSound() == targetRule.getNotificationSound() && - rule.shouldNotify() == targetRule.shouldNotify() && - rule.shouldNotNotify() == targetRule.shouldNotNotify()) + targetRule.enabled && + rule.getHighlight() == targetRule.getHighlight() && + rule.getNotificationSound() == targetRule.getNotificationSound() && + rule.shouldNotify() == targetRule.shouldNotify() && + rule.shouldNotNotify() == targetRule.shouldNotNotify()) } diff --git a/vector/src/main/java/im/vector/app/features/settings/notifications/PushRuleDefinitions.kt b/vector/src/main/java/im/vector/app/features/settings/notifications/PushRuleDefinitions.kt index d101d86aa3..1507528bd1 100644 --- a/vector/src/main/java/im/vector/app/features/settings/notifications/PushRuleDefinitions.kt +++ b/vector/src/main/java/im/vector/app/features/settings/notifications/PushRuleDefinitions.kt @@ -16,7 +16,7 @@ package im.vector.app.features.settings.notifications -import org.matrix.android.sdk.api.pushrules.RuleIds +import org.matrix.android.sdk.api.session.pushrules.RuleIds fun getStandardAction(ruleId: String, index: NotificationIndex): StandardActions? { return when (ruleId) { diff --git a/vector/src/main/java/im/vector/app/features/settings/notifications/StandardActions.kt b/vector/src/main/java/im/vector/app/features/settings/notifications/StandardActions.kt index d6b6165bd9..b19b5bcdd6 100644 --- a/vector/src/main/java/im/vector/app/features/settings/notifications/StandardActions.kt +++ b/vector/src/main/java/im/vector/app/features/settings/notifications/StandardActions.kt @@ -16,7 +16,7 @@ package im.vector.app.features.settings.notifications -import org.matrix.android.sdk.api.pushrules.Action +import org.matrix.android.sdk.api.session.pushrules.Action sealed class StandardActions( val actions: List? diff --git a/vector/src/main/java/im/vector/app/features/settings/notifications/VectorSettingsAdvancedNotificationPreferenceFragment.kt b/vector/src/main/java/im/vector/app/features/settings/notifications/VectorSettingsAdvancedNotificationPreferenceFragment.kt index 3b4aef929d..8eccc8c593 100644 --- a/vector/src/main/java/im/vector/app/features/settings/notifications/VectorSettingsAdvancedNotificationPreferenceFragment.kt +++ b/vector/src/main/java/im/vector/app/features/settings/notifications/VectorSettingsAdvancedNotificationPreferenceFragment.kt @@ -23,12 +23,12 @@ import im.vector.app.core.preference.VectorPreference import im.vector.app.core.utils.toast import im.vector.app.features.settings.VectorSettingsBaseFragment import kotlinx.coroutines.launch -import org.matrix.android.sdk.api.pushrules.RuleIds -import org.matrix.android.sdk.api.pushrules.rest.PushRuleAndKind +import org.matrix.android.sdk.api.session.pushrules.RuleIds +import org.matrix.android.sdk.api.session.pushrules.rest.PushRuleAndKind import javax.inject.Inject class VectorSettingsAdvancedNotificationPreferenceFragment @Inject constructor() : - VectorSettingsBaseFragment() { + VectorSettingsBaseFragment() { override var titleRes: Int = R.string.settings_notification_advanced @@ -38,7 +38,7 @@ class VectorSettingsAdvancedNotificationPreferenceFragment @Inject constructor() for (preferenceKey in prefKeyToPushRuleId.keys) { val preference = findPreference(preferenceKey) if (preference is PushRulePreference) { - val ruleAndKind: PushRuleAndKind? = session.getPushRules().findDefaultRule(prefKeyToPushRuleId[preferenceKey]) + val ruleAndKind: PushRuleAndKind? = session.pushRuleService().getPushRules().findDefaultRule(prefKeyToPushRuleId[preferenceKey]) if (ruleAndKind == null) { // The rule is not defined, hide the preference @@ -57,10 +57,12 @@ class VectorSettingsAdvancedNotificationPreferenceFragment @Inject constructor() lifecycleScope.launch { val result = runCatching { - session.updatePushRuleActions(ruleAndKind.kind, + session.pushRuleService().updatePushRuleActions( + ruleAndKind.kind, ruleAndKind.pushRule.ruleId, enabled, - newActions) + newActions + ) } if (!isAdded) { return@launch diff --git a/vector/src/main/java/im/vector/app/features/settings/notifications/VectorSettingsDefaultNotificationPreferenceFragment.kt b/vector/src/main/java/im/vector/app/features/settings/notifications/VectorSettingsDefaultNotificationPreferenceFragment.kt index 2179e0eee2..33ed7730eb 100644 --- a/vector/src/main/java/im/vector/app/features/settings/notifications/VectorSettingsDefaultNotificationPreferenceFragment.kt +++ b/vector/src/main/java/im/vector/app/features/settings/notifications/VectorSettingsDefaultNotificationPreferenceFragment.kt @@ -20,21 +20,21 @@ import android.os.Bundle import im.vector.app.R import im.vector.app.core.preference.VectorPreferenceCategory import im.vector.app.features.analytics.plan.MobileScreen -import org.matrix.android.sdk.api.pushrules.RuleIds +import org.matrix.android.sdk.api.session.pushrules.RuleIds class VectorSettingsDefaultNotificationPreferenceFragment : - VectorSettingsPushRuleNotificationPreferenceFragment() { + VectorSettingsPushRuleNotificationPreferenceFragment() { override var titleRes: Int = R.string.settings_notification_default override val preferenceXmlRes = R.xml.vector_settings_notification_default override val prefKeyToPushRuleId = mapOf( - "SETTINGS_PUSH_RULE_MESSAGES_IN_ONE_TO_ONE_PREFERENCE_KEY" to RuleIds.RULE_ID_ONE_TO_ONE_ROOM, - "SETTINGS_PUSH_RULE_MESSAGES_IN_GROUP_CHAT_PREFERENCE_KEY" to RuleIds.RULE_ID_ALL_OTHER_MESSAGES_ROOMS, - "SETTINGS_PUSH_RULE_MESSAGES_IN_E2E_ONE_ONE_CHAT_PREFERENCE_KEY" to RuleIds.RULE_ID_ONE_TO_ONE_ENCRYPTED_ROOM, - "SETTINGS_PUSH_RULE_MESSAGES_IN_E2E_GROUP_CHAT_PREFERENCE_KEY" to RuleIds.RULE_ID_ENCRYPTED - ) + "SETTINGS_PUSH_RULE_MESSAGES_IN_ONE_TO_ONE_PREFERENCE_KEY" to RuleIds.RULE_ID_ONE_TO_ONE_ROOM, + "SETTINGS_PUSH_RULE_MESSAGES_IN_GROUP_CHAT_PREFERENCE_KEY" to RuleIds.RULE_ID_ALL_OTHER_MESSAGES_ROOMS, + "SETTINGS_PUSH_RULE_MESSAGES_IN_E2E_ONE_ONE_CHAT_PREFERENCE_KEY" to RuleIds.RULE_ID_ONE_TO_ONE_ENCRYPTED_ROOM, + "SETTINGS_PUSH_RULE_MESSAGES_IN_E2E_GROUP_CHAT_PREFERENCE_KEY" to RuleIds.RULE_ID_ENCRYPTED + ) override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) diff --git a/vector/src/main/java/im/vector/app/features/settings/notifications/VectorSettingsKeywordAndMentionsNotificationPreferenceFragment.kt b/vector/src/main/java/im/vector/app/features/settings/notifications/VectorSettingsKeywordAndMentionsNotificationPreferenceFragment.kt index 731f4b3228..d0a1bff50c 100644 --- a/vector/src/main/java/im/vector/app/features/settings/notifications/VectorSettingsKeywordAndMentionsNotificationPreferenceFragment.kt +++ b/vector/src/main/java/im/vector/app/features/settings/notifications/VectorSettingsKeywordAndMentionsNotificationPreferenceFragment.kt @@ -29,10 +29,10 @@ import im.vector.app.features.analytics.plan.MobileScreen import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch import kotlinx.coroutines.withContext -import org.matrix.android.sdk.api.pushrules.RuleIds -import org.matrix.android.sdk.api.pushrules.RuleKind -import org.matrix.android.sdk.api.pushrules.rest.PushRule -import org.matrix.android.sdk.api.pushrules.toJson +import org.matrix.android.sdk.api.session.pushrules.RuleIds +import org.matrix.android.sdk.api.session.pushrules.RuleKind +import org.matrix.android.sdk.api.session.pushrules.rest.PushRule +import org.matrix.android.sdk.api.session.pushrules.toJson class VectorSettingsKeywordAndMentionsNotificationPreferenceFragment : VectorSettingsPushRuleNotificationPreferenceFragment() { @@ -50,7 +50,7 @@ class VectorSettingsKeywordAndMentionsNotificationPreferenceFragment : override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) - session.getKeywords().observe(viewLifecycleOwner, this::updateWithKeywords) + session.pushRuleService().getKeywords().observe(viewLifecycleOwner, this::updateWithKeywords) } override fun bindPref() { @@ -61,7 +61,7 @@ class VectorSettingsKeywordAndMentionsNotificationPreferenceFragment : val yourKeywordsCategory = findPreference("SETTINGS_YOUR_KEYWORDS")!! yourKeywordsCategory.isIconSpaceReserved = false - val keywordRules = session.getPushRules().content?.filter { !it.ruleId.startsWith(".") }.orEmpty() + val keywordRules = session.pushRuleService().getPushRules().content?.filter { !it.ruleId.startsWith(".") }.orEmpty() val enableKeywords = keywordRules.isEmpty() || keywordRules.any(PushRule::enabled) val editKeywordPreference = findPreference("SETTINGS_KEYWORD_EDIT")!! @@ -119,10 +119,12 @@ class VectorSettingsKeywordAndMentionsNotificationPreferenceFragment : val results = keywords.map { runCatching { withContext(Dispatchers.Default) { - session.updatePushRuleActions(RuleKind.CONTENT, + session.pushRuleService().updatePushRuleActions( + RuleKind.CONTENT, it, enabled, - newActions) + newActions + ) } } } @@ -151,7 +153,7 @@ class VectorSettingsKeywordAndMentionsNotificationPreferenceFragment : displayLoadingView() lifecycleScope.launch { val result = runCatching { - session.addPushRule(RuleKind.CONTENT, newRule) + session.pushRuleService().addPushRule(RuleKind.CONTENT, newRule) } hideLoadingView() if (!isAdded) { @@ -167,7 +169,7 @@ class VectorSettingsKeywordAndMentionsNotificationPreferenceFragment : displayLoadingView() lifecycleScope.launch { val result = runCatching { - session.removePushRule(RuleKind.CONTENT, keyword) + session.pushRuleService().removePushRule(RuleKind.CONTENT, keyword) } hideLoadingView() if (!isAdded) { diff --git a/vector/src/main/java/im/vector/app/features/settings/notifications/VectorSettingsNotificationPreferenceFragment.kt b/vector/src/main/java/im/vector/app/features/settings/notifications/VectorSettingsNotificationPreferenceFragment.kt index 79104433ee..b950ca4a32 100644 --- a/vector/src/main/java/im/vector/app/features/settings/notifications/VectorSettingsNotificationPreferenceFragment.kt +++ b/vector/src/main/java/im/vector/app/features/settings/notifications/VectorSettingsNotificationPreferenceFragment.kt @@ -53,11 +53,11 @@ import im.vector.app.push.fcm.FcmHelper import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.launch import org.matrix.android.sdk.api.extensions.tryOrNull -import org.matrix.android.sdk.api.pushrules.RuleIds -import org.matrix.android.sdk.api.pushrules.RuleKind import org.matrix.android.sdk.api.session.Session import org.matrix.android.sdk.api.session.identity.ThreePid import org.matrix.android.sdk.api.session.pushers.Pusher +import org.matrix.android.sdk.api.session.pushrules.RuleIds +import org.matrix.android.sdk.api.session.pushrules.RuleKind import javax.inject.Inject // Referenced in vector_settings_preferences_root.xml @@ -81,7 +81,7 @@ class VectorSettingsNotificationPreferenceFragment @Inject constructor( override fun bindPref() { findPreference(VectorPreferences.SETTINGS_ENABLE_ALL_NOTIF_PREFERENCE_KEY)!!.let { pref -> - val pushRuleService = session + val pushRuleService = session.pushRuleService() val mRuleMaster = pushRuleService.getPushRules().getAllRules() .find { it.ruleId == RuleIds.RULE_ID_DISABLE_ALL } @@ -104,7 +104,7 @@ class VectorSettingsNotificationPreferenceFragment @Inject constructor( } else { FcmHelper.getFcmToken(requireContext())?.let { pushManager.unregisterPusher(it) - session.refreshPushers() + session.pushersService().refreshPushers() } } } @@ -204,7 +204,7 @@ class VectorSettingsNotificationPreferenceFragment @Inject constructor( // Important, Battery optim white listing is needed in this mode; // Even if using foreground service with foreground notif, it stops to work // in doze mode for certain devices :/ - if (!isIgnoringBatteryOptimizations(requireContext())) { + if (!requireContext().isIgnoringBatteryOptimizations()) { requestDisablingBatteryOptimization(requireActivity(), batteryStartForActivityResult) } } @@ -241,7 +241,7 @@ class VectorSettingsNotificationPreferenceFragment @Inject constructor( } /** - * Convert a delay in seconds to string + * Convert a delay in seconds to string. * * @param seconds the delay in seconds * @return the text @@ -318,7 +318,7 @@ class VectorSettingsNotificationPreferenceFragment @Inject constructor( override fun onResume() { super.onResume() - activeSessionHolder.getSafeActiveSession()?.refreshPushers() + activeSessionHolder.getSafeActiveSession()?.pushersService()?.refreshPushers() interactionListener?.requestedKeyToHighlight()?.let { key -> interactionListener?.requestHighlightPreferenceKeyOnResume(null) @@ -352,8 +352,8 @@ class VectorSettingsNotificationPreferenceFragment @Inject constructor( super.onDetach() } - override fun onPreferenceTreeClick(preference: Preference?): Boolean { - return when (preference?.key) { + override fun onPreferenceTreeClick(preference: Preference): Boolean { + return when (preference.key) { VectorPreferences.SETTINGS_ENABLE_ALL_NOTIF_PREFERENCE_KEY -> { updateEnabledForAccount(preference) true @@ -365,7 +365,7 @@ class VectorSettingsNotificationPreferenceFragment @Inject constructor( } private fun updateEnabledForAccount(preference: Preference?) { - val pushRuleService = session + val pushRuleService = session.pushRuleService() val switchPref = preference as SwitchPreference pushRuleService.getPushRules().getAllRules() .find { it.ruleId == RuleIds.RULE_ID_DISABLE_ALL } @@ -373,9 +373,11 @@ class VectorSettingsNotificationPreferenceFragment @Inject constructor( // Trick, we must enable this room to disable notifications lifecycleScope.launch { try { - pushRuleService.updatePushRuleEnableStatus(RuleKind.OVERRIDE, + pushRuleService.updatePushRuleEnableStatus( + RuleKind.OVERRIDE, it, - !switchPref.isChecked) + !switchPref.isChecked + ) // Push rules will be updated from the sync } catch (failure: Throwable) { if (!isAdded) { @@ -414,15 +416,15 @@ private fun SwitchPreference.setTransactionalSwitchChangeListener(scope: Corouti * @see ThreePid.Email */ private fun Session.getEmailsWithPushInformation(): List> { - val emailPushers = getPushers().filter { it.kind == Pusher.KIND_EMAIL } - return getThreePids() + val emailPushers = pushersService().getPushers().filter { it.kind == Pusher.KIND_EMAIL } + return profileService().getThreePids() .filterIsInstance() .map { it to emailPushers.any { pusher -> pusher.pushKey == it.email } } } private fun Session.getEmailsWithPushInformationLive(): LiveData>> { - val emailThreePids = getThreePidsLive(refreshData = true).map { it.filterIsInstance() } - val emailPushers = getPushersLive().map { it.filter { pusher -> pusher.kind == Pusher.KIND_EMAIL } } + val emailThreePids = profileService().getThreePidsLive(refreshData = true).map { it.filterIsInstance() } + val emailPushers = pushersService().getPushersLive().map { it.filter { pusher -> pusher.kind == Pusher.KIND_EMAIL } } return combineLatest(emailThreePids, emailPushers) { emailThreePidsResult, emailPushersResult -> emailThreePidsResult.map { it to emailPushersResult.any { pusher -> pusher.pushKey == it.email } } }.distinctUntilChanged() diff --git a/vector/src/main/java/im/vector/app/features/settings/notifications/VectorSettingsOtherNotificationPreferenceFragment.kt b/vector/src/main/java/im/vector/app/features/settings/notifications/VectorSettingsOtherNotificationPreferenceFragment.kt index 71f8f0920a..3638fb8526 100644 --- a/vector/src/main/java/im/vector/app/features/settings/notifications/VectorSettingsOtherNotificationPreferenceFragment.kt +++ b/vector/src/main/java/im/vector/app/features/settings/notifications/VectorSettingsOtherNotificationPreferenceFragment.kt @@ -18,21 +18,21 @@ package im.vector.app.features.settings.notifications import im.vector.app.R import im.vector.app.core.preference.VectorPreferenceCategory -import org.matrix.android.sdk.api.pushrules.RuleIds +import org.matrix.android.sdk.api.session.pushrules.RuleIds class VectorSettingsOtherNotificationPreferenceFragment : - VectorSettingsPushRuleNotificationPreferenceFragment() { + VectorSettingsPushRuleNotificationPreferenceFragment() { override var titleRes: Int = R.string.settings_notification_other override val preferenceXmlRes = R.xml.vector_settings_notification_other override val prefKeyToPushRuleId = mapOf( - "SETTINGS_PUSH_RULE_INVITED_TO_ROOM_PREFERENCE_KEY" to RuleIds.RULE_ID_INVITE_ME, - "SETTINGS_PUSH_RULE_CALL_INVITATIONS_PREFERENCE_KEY" to RuleIds.RULE_ID_CALL, - "SETTINGS_PUSH_RULE_MESSAGES_SENT_BY_BOT_PREFERENCE_KEY" to RuleIds.RULE_ID_SUPPRESS_BOTS_NOTIFICATIONS, - "SETTINGS_PUSH_RULE_ROOMS_UPGRADED_KEY" to RuleIds.RULE_ID_TOMBSTONE - ) + "SETTINGS_PUSH_RULE_INVITED_TO_ROOM_PREFERENCE_KEY" to RuleIds.RULE_ID_INVITE_ME, + "SETTINGS_PUSH_RULE_CALL_INVITATIONS_PREFERENCE_KEY" to RuleIds.RULE_ID_CALL, + "SETTINGS_PUSH_RULE_MESSAGES_SENT_BY_BOT_PREFERENCE_KEY" to RuleIds.RULE_ID_SUPPRESS_BOTS_NOTIFICATIONS, + "SETTINGS_PUSH_RULE_ROOMS_UPGRADED_KEY" to RuleIds.RULE_ID_TOMBSTONE + ) override fun bindPref() { super.bindPref() diff --git a/vector/src/main/java/im/vector/app/features/settings/notifications/VectorSettingsPushRuleNotificationPreferenceFragment.kt b/vector/src/main/java/im/vector/app/features/settings/notifications/VectorSettingsPushRuleNotificationPreferenceFragment.kt index 26ee2fc601..7f856298ea 100644 --- a/vector/src/main/java/im/vector/app/features/settings/notifications/VectorSettingsPushRuleNotificationPreferenceFragment.kt +++ b/vector/src/main/java/im/vector/app/features/settings/notifications/VectorSettingsPushRuleNotificationPreferenceFragment.kt @@ -21,11 +21,11 @@ import androidx.preference.Preference import im.vector.app.core.preference.VectorCheckboxPreference import im.vector.app.features.settings.VectorSettingsBaseFragment import kotlinx.coroutines.launch -import org.matrix.android.sdk.api.pushrules.RuleKind -import org.matrix.android.sdk.api.pushrules.rest.PushRuleAndKind +import org.matrix.android.sdk.api.session.pushrules.RuleKind +import org.matrix.android.sdk.api.session.pushrules.rest.PushRuleAndKind abstract class VectorSettingsPushRuleNotificationPreferenceFragment : - VectorSettingsBaseFragment() { + VectorSettingsBaseFragment() { abstract val prefKeyToPushRuleId: Map @@ -33,7 +33,7 @@ abstract class VectorSettingsPushRuleNotificationPreferenceFragment : for (preferenceKey in prefKeyToPushRuleId.keys) { val preference = findPreference(preferenceKey)!! preference.isIconSpaceReserved = false - val ruleAndKind: PushRuleAndKind? = session.getPushRules().findDefaultRule(prefKeyToPushRuleId[preferenceKey]) + val ruleAndKind: PushRuleAndKind? = session.pushRuleService().getPushRules().findDefaultRule(prefKeyToPushRuleId[preferenceKey]) if (ruleAndKind == null) { // The rule is not defined, hide the preference preference.isVisible = false @@ -58,10 +58,12 @@ abstract class VectorSettingsPushRuleNotificationPreferenceFragment : lifecycleScope.launch { val result = runCatching { - session.updatePushRuleActions(kind, + session.pushRuleService().updatePushRuleActions( + kind, ruleId, enabled, - newActions) + newActions + ) } hideLoadingView() if (!isAdded) { diff --git a/vector/src/main/java/im/vector/app/features/settings/push/PushGatewaysViewModel.kt b/vector/src/main/java/im/vector/app/features/settings/push/PushGatewaysViewModel.kt index 4d95447f2d..14b7a53b65 100644 --- a/vector/src/main/java/im/vector/app/features/settings/push/PushGatewaysViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/settings/push/PushGatewaysViewModel.kt @@ -49,7 +49,7 @@ class PushGatewaysViewModel @AssistedInject constructor(@Assisted initialState: init { observePushers() // Force a refresh - session.refreshPushers() + session.pushersService().refreshPushers() } private fun observePushers() { @@ -70,7 +70,7 @@ class PushGatewaysViewModel @AssistedInject constructor(@Assisted initialState: private fun removePusher(pusher: Pusher) { viewModelScope.launch { kotlin.runCatching { - session.removePusher(pusher) + session.pushersService().removePusher(pusher) }.onFailure { _viewEvents.post(PushGatewayViewEvents.RemovePusherFailed(it)) } @@ -78,6 +78,6 @@ class PushGatewaysViewModel @AssistedInject constructor(@Assisted initialState: } private fun handleRefresh() { - session.refreshPushers() + session.pushersService().refreshPushers() } } diff --git a/vector/src/main/java/im/vector/app/features/settings/push/PushRuleItem.kt b/vector/src/main/java/im/vector/app/features/settings/push/PushRuleItem.kt index f6e03143f5..d56562a7dd 100644 --- a/vector/src/main/java/im/vector/app/features/settings/push/PushRuleItem.kt +++ b/vector/src/main/java/im/vector/app/features/settings/push/PushRuleItem.kt @@ -30,8 +30,8 @@ import im.vector.app.R import im.vector.app.core.epoxy.VectorEpoxyHolder import im.vector.app.features.notifications.toNotificationAction import im.vector.app.features.themes.ThemeUtils -import org.matrix.android.sdk.api.pushrules.getActions -import org.matrix.android.sdk.api.pushrules.rest.PushRule +import org.matrix.android.sdk.api.session.pushrules.getActions +import org.matrix.android.sdk.api.session.pushrules.rest.PushRule @EpoxyModelClass(layout = R.layout.item_pushrule_raw) abstract class PushRuleItem : EpoxyModelWithHolder() { @@ -72,8 +72,10 @@ abstract class PushRuleItem : EpoxyModelWithHolder() { val description = StringBuffer() pushRule.conditions?.forEachIndexed { i, condition -> if (i > 0) description.append("\n") - description.append(condition.asExecutableCondition(pushRule)?.technicalDescription() - ?: "UNSUPPORTED") + description.append( + condition.asExecutableCondition(pushRule)?.technicalDescription() + ?: "UNSUPPORTED" + ) } if (description.isBlank()) { holder.description.text = "No Conditions" diff --git a/vector/src/main/java/im/vector/app/features/settings/push/PushRulesViewModel.kt b/vector/src/main/java/im/vector/app/features/settings/push/PushRulesViewModel.kt index 0b6b72bb10..deda34115e 100644 --- a/vector/src/main/java/im/vector/app/features/settings/push/PushRulesViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/settings/push/PushRulesViewModel.kt @@ -23,20 +23,20 @@ import im.vector.app.core.di.SingletonEntryPoint import im.vector.app.core.platform.EmptyAction import im.vector.app.core.platform.EmptyViewEvents import im.vector.app.core.platform.VectorViewModel -import org.matrix.android.sdk.api.pushrules.rest.PushRule +import org.matrix.android.sdk.api.session.pushrules.rest.PushRule data class PushRulesViewState( val rules: List = emptyList() ) : MavericksState class PushRulesViewModel(initialState: PushRulesViewState) : - VectorViewModel(initialState) { + VectorViewModel(initialState) { companion object : MavericksViewModelFactory { override fun initialState(viewModelContext: ViewModelContext): PushRulesViewState? { val session = EntryPoints.get(viewModelContext.app(), SingletonEntryPoint::class.java).activeSessionHolder().getActiveSession() - val rules = session.getPushRules().getAllRules() + val rules = session.pushRuleService().getPushRules().getAllRules() return PushRulesViewState(rules) } } diff --git a/vector/src/main/java/im/vector/app/features/settings/threepids/ThreePidsSettingsFragment.kt b/vector/src/main/java/im/vector/app/features/settings/threepids/ThreePidsSettingsFragment.kt index ee7f8efab4..4a3f895c51 100644 --- a/vector/src/main/java/im/vector/app/features/settings/threepids/ThreePidsSettingsFragment.kt +++ b/vector/src/main/java/im/vector/app/features/settings/threepids/ThreePidsSettingsFragment.kt @@ -61,24 +61,27 @@ class ThreePidsSettingsFragment @Inject constructor( viewModel.observeViewEvents { when (it) { - is ThreePidsSettingsViewEvents.Failure -> displayErrorDialog(it.throwable) + is ThreePidsSettingsViewEvents.Failure -> displayErrorDialog(it.throwable) is ThreePidsSettingsViewEvents.RequestReAuth -> askAuthentication(it) } } } private fun askAuthentication(event: ThreePidsSettingsViewEvents.RequestReAuth) { - ReAuthActivity.newIntent(requireContext(), + ReAuthActivity.newIntent( + requireContext(), event.registrationFlowResponse, event.lastErrorCode, - getString(R.string.settings_add_email_address)).let { intent -> + getString(R.string.settings_add_email_address) + ).let { intent -> reAuthActivityResultLauncher.launch(intent) } } + private val reAuthActivityResultLauncher = registerStartForActivityResult { activityResult -> if (activityResult.resultCode == Activity.RESULT_OK) { when (activityResult.data?.extras?.getString(ReAuthActivity.RESULT_FLOW_TYPE)) { - LoginFlowTypes.SSO -> { + LoginFlowTypes.SSO -> { viewModel.handle(ThreePidsSettingsAction.SsoAuthDone) } LoginFlowTypes.PASSWORD -> { diff --git a/vector/src/main/java/im/vector/app/features/settings/threepids/ThreePidsSettingsViewEvents.kt b/vector/src/main/java/im/vector/app/features/settings/threepids/ThreePidsSettingsViewEvents.kt index 94ea8d24c6..9768d7bd93 100644 --- a/vector/src/main/java/im/vector/app/features/settings/threepids/ThreePidsSettingsViewEvents.kt +++ b/vector/src/main/java/im/vector/app/features/settings/threepids/ThreePidsSettingsViewEvents.kt @@ -22,6 +22,6 @@ import org.matrix.android.sdk.api.auth.registration.RegistrationFlowResponse sealed class ThreePidsSettingsViewEvents : VectorViewEvents { data class Failure(val throwable: Throwable) : ThreePidsSettingsViewEvents() -// object RequestPassword : ThreePidsSettingsViewEvents() + // object RequestPassword : ThreePidsSettingsViewEvents() data class RequestReAuth(val registrationFlowResponse: RegistrationFlowResponse, val lastErrorCode: String?) : ThreePidsSettingsViewEvents() } diff --git a/vector/src/main/java/im/vector/app/features/settings/threepids/ThreePidsSettingsViewModel.kt b/vector/src/main/java/im/vector/app/features/settings/threepids/ThreePidsSettingsViewModel.kt index 0af0a8baa5..6f26dd2b15 100644 --- a/vector/src/main/java/im/vector/app/features/settings/threepids/ThreePidsSettingsViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/settings/threepids/ThreePidsSettingsViewModel.kt @@ -133,7 +133,8 @@ class ThreePidsSettingsViewModel @AssistedInject constructor( } } is ThreePidsSettingsAction.PasswordAuthDone -> { - val decryptedPass = session.loadSecureSecret(action.password.fromBase64().inputStream(), ReAuthActivity.DEFAULT_RESULT_KEYSTORE_ALIAS) + val decryptedPass = session.secureStorageService() + .loadSecureSecret(action.password.fromBase64().inputStream(), ReAuthActivity.DEFAULT_RESULT_KEYSTORE_ALIAS) uiaContinuation?.resume( UserPasswordAuth( session = pendingAuth?.session, @@ -175,7 +176,7 @@ class ThreePidsSettingsViewModel @AssistedInject constructor( viewModelScope.launch { // First submit the code try { - session.submitSmsCode(action.threePid, action.code) + session.profileService().submitSmsCode(action.threePid, action.code) } catch (failure: Throwable) { isLoading(false) setState { @@ -190,7 +191,7 @@ class ThreePidsSettingsViewModel @AssistedInject constructor( // then finalize pendingThreePid = action.threePid - loadingSuspendable { session.finalizeAddingThreePid(action.threePid, uiaInterceptor) } + loadingSuspendable { session.profileService().finalizeAddingThreePid(action.threePid, uiaInterceptor) } } } @@ -209,16 +210,22 @@ class ThreePidsSettingsViewModel @AssistedInject constructor( withState { state -> val allThreePids = state.threePids.invoke().orEmpty() + state.pendingThreePids.invoke().orEmpty() if (allThreePids.any { it.value == action.threePid.value }) { - _viewEvents.post(ThreePidsSettingsViewEvents.Failure(IllegalArgumentException(stringProvider.getString( - when (action.threePid) { - is ThreePid.Email -> R.string.auth_email_already_defined - is ThreePid.Msisdn -> R.string.auth_msisdn_already_defined - } - )))) + _viewEvents.post( + ThreePidsSettingsViewEvents.Failure( + IllegalArgumentException( + stringProvider.getString( + when (action.threePid) { + is ThreePid.Email -> R.string.auth_email_already_defined + is ThreePid.Msisdn -> R.string.auth_msisdn_already_defined + } + ) + ) + ) + ) } else { viewModelScope.launch { loadingSuspendable { - session.addThreePid(action.threePid) + session.profileService().addThreePid(action.threePid) // Also reset the state setState { copy( @@ -235,14 +242,14 @@ class ThreePidsSettingsViewModel @AssistedInject constructor( isLoading(true) pendingThreePid = action.threePid viewModelScope.launch { - loadingSuspendable { session.finalizeAddingThreePid(action.threePid, uiaInterceptor) } + loadingSuspendable { session.profileService().finalizeAddingThreePid(action.threePid, uiaInterceptor) } } } private fun handleCancelThreePid(action: ThreePidsSettingsAction.CancelThreePid) { isLoading(true) viewModelScope.launch { - loadingSuspendable { session.cancelAddingThreePid(action.threePid) } + loadingSuspendable { session.profileService().cancelAddingThreePid(action.threePid) } } } @@ -258,7 +265,7 @@ class ThreePidsSettingsViewModel @AssistedInject constructor( private fun handleDeleteThreePid(action: ThreePidsSettingsAction.DeleteThreePid) { isLoading(true) viewModelScope.launch { - loadingSuspendable { session.deleteThreePid(action.threePid) } + loadingSuspendable { session.profileService().deleteThreePid(action.threePid) } } } } diff --git a/vector/src/main/java/im/vector/app/features/settings/troubleshoot/NotificationTroubleshootRecyclerViewAdapter.kt b/vector/src/main/java/im/vector/app/features/settings/troubleshoot/NotificationTroubleshootRecyclerViewAdapter.kt index 8f0b2cfe74..8f762a9abc 100644 --- a/vector/src/main/java/im/vector/app/features/settings/troubleshoot/NotificationTroubleshootRecyclerViewAdapter.kt +++ b/vector/src/main/java/im/vector/app/features/settings/troubleshoot/NotificationTroubleshootRecyclerViewAdapter.kt @@ -25,7 +25,7 @@ import im.vector.app.databinding.ItemNotificationTroubleshootBinding import im.vector.app.features.themes.ThemeUtils class NotificationTroubleshootRecyclerViewAdapter(val tests: ArrayList) : - RecyclerView.Adapter() { + RecyclerView.Adapter() { override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder { val inflater = LayoutInflater.from(parent.context) diff --git a/vector/src/main/java/im/vector/app/features/settings/troubleshoot/TestAccountSettings.kt b/vector/src/main/java/im/vector/app/features/settings/troubleshoot/TestAccountSettings.kt index 9d06e1724c..c567026eb1 100644 --- a/vector/src/main/java/im/vector/app/features/settings/troubleshoot/TestAccountSettings.kt +++ b/vector/src/main/java/im/vector/app/features/settings/troubleshoot/TestAccountSettings.kt @@ -25,20 +25,20 @@ import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch import kotlinx.coroutines.withContext import org.matrix.android.sdk.api.extensions.tryOrNull -import org.matrix.android.sdk.api.pushrules.RuleIds -import org.matrix.android.sdk.api.pushrules.RuleKind +import org.matrix.android.sdk.api.session.pushrules.RuleIds +import org.matrix.android.sdk.api.session.pushrules.RuleKind import javax.inject.Inject /** - * Check that the main pushRule (RULE_ID_DISABLE_ALL) is correctly setup + * Check that the main pushRule (RULE_ID_DISABLE_ALL) is correctly setup. */ class TestAccountSettings @Inject constructor(private val stringProvider: StringProvider, private val activeSessionHolder: ActiveSessionHolder) : - TroubleshootTest(R.string.settings_troubleshoot_test_account_settings_title) { + TroubleshootTest(R.string.settings_troubleshoot_test_account_settings_title) { override fun perform(activityResultLauncher: ActivityResultLauncher) { val session = activeSessionHolder.getSafeActiveSession() ?: return - val defaultRule = session.getPushRules().getAllRules() + val defaultRule = session.pushRuleService().getPushRules().getAllRules() .find { it.ruleId == RuleIds.RULE_ID_DISABLE_ALL } if (defaultRule != null) { @@ -54,7 +54,7 @@ class TestAccountSettings @Inject constructor(private val stringProvider: String session.coroutineScope.launch { tryOrNull { - session.updatePushRuleEnableStatus(RuleKind.OVERRIDE, defaultRule, !defaultRule.enabled) + session.pushRuleService().updatePushRuleEnableStatus(RuleKind.OVERRIDE, defaultRule, !defaultRule.enabled) } withContext(Dispatchers.Main) { manager?.retry(activityResultLauncher) diff --git a/vector/src/main/java/im/vector/app/features/settings/troubleshoot/TestDeviceSettings.kt b/vector/src/main/java/im/vector/app/features/settings/troubleshoot/TestDeviceSettings.kt index f9fc7e80e3..c58b7f4ebc 100644 --- a/vector/src/main/java/im/vector/app/features/settings/troubleshoot/TestDeviceSettings.kt +++ b/vector/src/main/java/im/vector/app/features/settings/troubleshoot/TestDeviceSettings.kt @@ -27,7 +27,7 @@ import javax.inject.Inject */ class TestDeviceSettings @Inject constructor(private val vectorPreferences: VectorPreferences, private val stringProvider: StringProvider) : - TroubleshootTest(R.string.settings_troubleshoot_test_device_settings_title) { + TroubleshootTest(R.string.settings_troubleshoot_test_device_settings_title) { override fun perform(activityResultLauncher: ActivityResultLauncher) { if (vectorPreferences.areNotificationEnabledForDevice()) { diff --git a/vector/src/main/java/im/vector/app/features/settings/troubleshoot/TestNotification.kt b/vector/src/main/java/im/vector/app/features/settings/troubleshoot/TestNotification.kt index 412916c201..c2e5dc4997 100644 --- a/vector/src/main/java/im/vector/app/features/settings/troubleshoot/TestNotification.kt +++ b/vector/src/main/java/im/vector/app/features/settings/troubleshoot/TestNotification.kt @@ -25,12 +25,12 @@ import im.vector.app.features.notifications.NotificationUtils import javax.inject.Inject /** - * Checks if notifications can be displayed and clicked by the user + * Checks if notifications can be displayed and clicked by the user. */ class TestNotification @Inject constructor(private val context: Context, private val notificationUtils: NotificationUtils, private val stringProvider: StringProvider) : - TroubleshootTest(R.string.settings_troubleshoot_test_notification_title) { + TroubleshootTest(R.string.settings_troubleshoot_test_notification_title) { override fun perform(activityResultLauncher: ActivityResultLauncher) { // Display the notification right now diff --git a/vector/src/main/java/im/vector/app/features/settings/troubleshoot/TestPushRulesSettings.kt b/vector/src/main/java/im/vector/app/features/settings/troubleshoot/TestPushRulesSettings.kt index 79e4377a5c..ae57babf9a 100644 --- a/vector/src/main/java/im/vector/app/features/settings/troubleshoot/TestPushRulesSettings.kt +++ b/vector/src/main/java/im/vector/app/features/settings/troubleshoot/TestPushRulesSettings.kt @@ -21,23 +21,25 @@ import im.vector.app.R import im.vector.app.core.di.ActiveSessionHolder import im.vector.app.core.resources.StringProvider import im.vector.app.features.notifications.toNotificationAction -import org.matrix.android.sdk.api.pushrules.RuleIds -import org.matrix.android.sdk.api.pushrules.getActions +import org.matrix.android.sdk.api.session.pushrules.RuleIds +import org.matrix.android.sdk.api.session.pushrules.getActions import javax.inject.Inject class TestPushRulesSettings @Inject constructor(private val activeSessionHolder: ActiveSessionHolder, private val stringProvider: StringProvider) : - TroubleshootTest(R.string.settings_troubleshoot_test_bing_settings_title) { + TroubleshootTest(R.string.settings_troubleshoot_test_bing_settings_title) { private val testedRules = - listOf(RuleIds.RULE_ID_CONTAIN_DISPLAY_NAME, + listOf( + RuleIds.RULE_ID_CONTAIN_DISPLAY_NAME, RuleIds.RULE_ID_CONTAIN_USER_NAME, RuleIds.RULE_ID_ONE_TO_ONE_ROOM, - RuleIds.RULE_ID_ALL_OTHER_MESSAGES_ROOMS) + RuleIds.RULE_ID_ALL_OTHER_MESSAGES_ROOMS + ) override fun perform(activityResultLauncher: ActivityResultLauncher) { val session = activeSessionHolder.getSafeActiveSession() ?: return - val pushRules = session.getPushRules().getAllRules() + val pushRules = session.pushRuleService().getPushRules().getAllRules() var oneOrMoreRuleIsOff = false var oneOrMoreRuleAreSilent = false testedRules.forEach { ruleId -> diff --git a/vector/src/main/java/im/vector/app/features/settings/troubleshoot/TestSystemSettings.kt b/vector/src/main/java/im/vector/app/features/settings/troubleshoot/TestSystemSettings.kt index 42f506d4a6..f3e64659cf 100644 --- a/vector/src/main/java/im/vector/app/features/settings/troubleshoot/TestSystemSettings.kt +++ b/vector/src/main/java/im/vector/app/features/settings/troubleshoot/TestSystemSettings.kt @@ -29,7 +29,7 @@ import javax.inject.Inject */ class TestSystemSettings @Inject constructor(private val context: FragmentActivity, private val stringProvider: StringProvider) : - TroubleshootTest(R.string.settings_troubleshoot_test_system_settings_title) { + TroubleshootTest(R.string.settings_troubleshoot_test_system_settings_title) { override fun perform(activityResultLauncher: ActivityResultLauncher) { if (NotificationManagerCompat.from(context).areNotificationsEnabled()) { diff --git a/vector/src/main/java/im/vector/app/features/share/IncomingShareFragment.kt b/vector/src/main/java/im/vector/app/features/share/IncomingShareFragment.kt index 9dc433e96f..a8504142b5 100644 --- a/vector/src/main/java/im/vector/app/features/share/IncomingShareFragment.kt +++ b/vector/src/main/java/im/vector/app/features/share/IncomingShareFragment.kt @@ -37,16 +37,18 @@ import im.vector.app.core.extensions.configureWith import im.vector.app.core.extensions.registerStartForActivityResult import im.vector.app.core.platform.VectorBaseFragment import im.vector.app.databinding.FragmentIncomingShareBinding +import im.vector.app.features.analytics.plan.ViewRoom import im.vector.app.features.attachments.AttachmentsHelper import im.vector.app.features.attachments.preview.AttachmentsPreviewActivity import im.vector.app.features.attachments.preview.AttachmentsPreviewArgs import org.matrix.android.sdk.api.session.content.ContentAttachmentData +import org.matrix.android.sdk.api.session.getRoomSummary import org.matrix.android.sdk.api.session.room.model.RoomSummary import javax.inject.Inject /** - * Display the list of rooms - * The user can select multiple rooms to send the data to + * Display the list of rooms. + * The user can select multiple rooms to send the data to. */ class IncomingShareFragment @Inject constructor( private val incomingShareController: IncomingShareController, @@ -124,7 +126,11 @@ class IncomingShareFragment @Inject constructor( private fun handleMultipleRoomsShareDone(viewEvent: IncomingShareViewEvents.MultipleRoomsShareDone) { requireActivity().let { - navigator.openRoom(it, viewEvent.roomId) + navigator.openRoom( + context = it, + roomId = viewEvent.roomId, + trigger = ViewRoom.Trigger.MobileLinkShare + ) it.finish() } } diff --git a/vector/src/main/java/im/vector/app/features/share/IncomingShareViewModel.kt b/vector/src/main/java/im/vector/app/features/share/IncomingShareViewModel.kt index ca4148ebb7..d184713f60 100644 --- a/vector/src/main/java/im/vector/app/features/share/IncomingShareViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/share/IncomingShareViewModel.kt @@ -34,6 +34,7 @@ import kotlinx.coroutines.flow.sample import org.matrix.android.sdk.api.query.QueryStringValue import org.matrix.android.sdk.api.session.Session import org.matrix.android.sdk.api.session.content.ContentAttachmentData +import org.matrix.android.sdk.api.session.getRoom import org.matrix.android.sdk.api.session.room.model.Membership import org.matrix.android.sdk.api.session.room.roomSummaryQueryParams import org.matrix.android.sdk.flow.flow @@ -42,7 +43,7 @@ class IncomingShareViewModel @AssistedInject constructor( @Assisted initialState: IncomingShareViewState, private val session: Session, private val breadcrumbsRoomComparator: BreadcrumbsRoomComparator) : - VectorViewModel(initialState) { + VectorViewModel(initialState) { @AssistedFactory interface Factory : MavericksAssistedViewModelFactory { @@ -118,7 +119,7 @@ class IncomingShareViewModel @AssistedInject constructor( is SharedData.Text -> { state.selectedRoomIds.forEach { roomId -> val room = session.getRoom(roomId) - room?.sendTextMessage(sharedData.text) + room?.sendService()?.sendTextMessage(sharedData.text) } // This is it, pass the first roomId to let the screen open it _viewEvents.post(IncomingShareViewEvents.MultipleRoomsShareDone(state.selectedRoomIds.first())) @@ -152,6 +153,7 @@ class IncomingShareViewModel @AssistedInject constructor( // Pick the first room to send the media selectedRoomIds.firstOrNull() ?.let { roomId -> session.getRoom(roomId) } + ?.sendService() ?.sendMedias(grouped.notPreviewables, compressMediaBeforeSending, selectedRoomIds) // Ensure they will not be sent twice @@ -172,6 +174,7 @@ class IncomingShareViewModel @AssistedInject constructor( // Pick the first room to send the media selectedRoomIds.firstOrNull() ?.let { roomId -> session.getRoom(roomId) } + ?.sendService() ?.sendMedias(attachmentData, compressMediaBeforeSending, selectedRoomIds) // This is it, pass the first roomId to let the screen open it _viewEvents.post(IncomingShareViewEvents.MultipleRoomsShareDone(selectedRoomIds.first())) diff --git a/vector/src/main/java/im/vector/app/features/signout/hard/SignedOutActivity.kt b/vector/src/main/java/im/vector/app/features/signout/hard/SignedOutActivity.kt index 87b8c33aa3..600eb0e992 100644 --- a/vector/src/main/java/im/vector/app/features/signout/hard/SignedOutActivity.kt +++ b/vector/src/main/java/im/vector/app/features/signout/hard/SignedOutActivity.kt @@ -28,7 +28,7 @@ import org.matrix.android.sdk.api.failure.GlobalError import timber.log.Timber /** - * In this screen, the user is viewing a message informing that he has been logged out + * In this screen, the user is viewing a message informing that he has been logged out. */ @AndroidEntryPoint class SignedOutActivity : VectorBaseActivity() { diff --git a/vector/src/main/java/im/vector/app/features/signout/soft/SoftLogoutActivity.kt b/vector/src/main/java/im/vector/app/features/signout/soft/SoftLogoutActivity.kt index 9acc81d0c2..158a818053 100644 --- a/vector/src/main/java/im/vector/app/features/signout/soft/SoftLogoutActivity.kt +++ b/vector/src/main/java/im/vector/app/features/signout/soft/SoftLogoutActivity.kt @@ -36,8 +36,8 @@ import timber.log.Timber import javax.inject.Inject /** - * In this screen, the user is viewing a message informing that he has been logged out - * Extends LoginActivity to get the login with SSO and forget password functionality for (nearly) free + * In this screen, the user is viewing a message informing that he has been logged out. + * Extends LoginActivity to get the login with SSO and forget password functionality for (nearly) free. */ @AndroidEntryPoint class SoftLogoutActivity : LoginActivity() { @@ -66,10 +66,12 @@ class SoftLogoutActivity : LoginActivity() { supportFragmentManager.popBackStack(null, FragmentManager.POP_BACK_STACK_INCLUSIVE) // And inform the user - showError(getString( - R.string.soft_logout_sso_not_same_user_error, - softLogoutViewEvents.currentUserId, - softLogoutViewEvents.newUserId) + showError( + getString( + R.string.soft_logout_sso_not_same_user_error, + softLogoutViewEvents.currentUserId, + softLogoutViewEvents.newUserId + ) ) } is SoftLogoutViewEvents.ClearData -> { diff --git a/vector/src/main/java/im/vector/app/features/signout/soft/SoftLogoutController.kt b/vector/src/main/java/im/vector/app/features/signout/soft/SoftLogoutController.kt index e2f3c14e7d..daca308ac1 100644 --- a/vector/src/main/java/im/vector/app/features/signout/soft/SoftLogoutController.kt +++ b/vector/src/main/java/im/vector/app/features/signout/soft/SoftLogoutController.kt @@ -74,10 +74,14 @@ class SoftLogoutController @Inject constructor( } loginTextItem { id("signText1") - text(host.stringProvider.getString(R.string.soft_logout_signin_notice, - state.homeServerUrl.toReducedUrl(), - state.userDisplayName, - state.userId)) + text( + host.stringProvider.getString( + R.string.soft_logout_signin_notice, + state.homeServerUrl.toReducedUrl(), + state.userDisplayName, + state.userId + ) + ) } if (state.hasUnsavedKeys) { loginTextItem { diff --git a/vector/src/main/java/im/vector/app/features/signout/soft/SoftLogoutFragment.kt b/vector/src/main/java/im/vector/app/features/signout/soft/SoftLogoutFragment.kt index f40f35a6e2..ea08640ab7 100644 --- a/vector/src/main/java/im/vector/app/features/signout/soft/SoftLogoutFragment.kt +++ b/vector/src/main/java/im/vector/app/features/signout/soft/SoftLogoutFragment.kt @@ -59,28 +59,34 @@ class SoftLogoutFragment @Inject constructor( softLogoutController.update(softLogoutViewState) when (val mode = softLogoutViewState.asyncHomeServerLoginFlowRequest.invoke()) { is LoginMode.SsoAndPassword -> { - loginViewModel.handle(LoginAction.SetupSsoForSessionRecovery( - softLogoutViewState.homeServerUrl, - softLogoutViewState.deviceId, - mode.ssoIdentityProviders - )) + loginViewModel.handle( + LoginAction.SetupSsoForSessionRecovery( + softLogoutViewState.homeServerUrl, + softLogoutViewState.deviceId, + mode.ssoIdentityProviders + ) + ) } - is LoginMode.Sso -> { - loginViewModel.handle(LoginAction.SetupSsoForSessionRecovery( - softLogoutViewState.homeServerUrl, - softLogoutViewState.deviceId, - mode.ssoIdentityProviders - )) + is LoginMode.Sso -> { + loginViewModel.handle( + LoginAction.SetupSsoForSessionRecovery( + softLogoutViewState.homeServerUrl, + softLogoutViewState.deviceId, + mode.ssoIdentityProviders + ) + ) } - LoginMode.Unsupported -> { + LoginMode.Unsupported -> { // Prepare the loginViewModel for a SSO/login fallback recovery - loginViewModel.handle(LoginAction.SetupSsoForSessionRecovery( - softLogoutViewState.homeServerUrl, - softLogoutViewState.deviceId, - null - )) + loginViewModel.handle( + LoginAction.SetupSsoForSessionRecovery( + softLogoutViewState.homeServerUrl, + softLogoutViewState.deviceId, + null + ) + ) } - else -> Unit + else -> Unit } } } diff --git a/vector/src/main/java/im/vector/app/features/signout/soft/SoftLogoutViewEvents.kt b/vector/src/main/java/im/vector/app/features/signout/soft/SoftLogoutViewEvents.kt index df4d556a48..adca250a3d 100644 --- a/vector/src/main/java/im/vector/app/features/signout/soft/SoftLogoutViewEvents.kt +++ b/vector/src/main/java/im/vector/app/features/signout/soft/SoftLogoutViewEvents.kt @@ -20,7 +20,7 @@ package im.vector.app.features.signout.soft import im.vector.app.core.platform.VectorViewEvents /** - * Transient events for SoftLogout + * Transient events for SoftLogout. */ sealed class SoftLogoutViewEvents : VectorViewEvents { data class Failure(val throwable: Throwable) : SoftLogoutViewEvents() diff --git a/vector/src/main/java/im/vector/app/features/signout/soft/SoftLogoutViewModel.kt b/vector/src/main/java/im/vector/app/features/signout/soft/SoftLogoutViewModel.kt index 00422d8872..1d67f1ec98 100644 --- a/vector/src/main/java/im/vector/app/features/signout/soft/SoftLogoutViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/signout/soft/SoftLogoutViewModel.kt @@ -37,6 +37,7 @@ import kotlinx.coroutines.launch import org.matrix.android.sdk.api.auth.AuthenticationService import org.matrix.android.sdk.api.auth.data.LoginFlowTypes import org.matrix.android.sdk.api.session.Session +import org.matrix.android.sdk.api.session.getUser import timber.log.Timber /** @@ -158,9 +159,12 @@ class SoftLogoutViewModel @AssistedInject constructor( withState { softLogoutViewState -> if (softLogoutViewState.userId != action.credentials.userId) { Timber.w("User login again with SSO, but using another account") - _viewEvents.post(SoftLogoutViewEvents.ErrorNotSameUser( - softLogoutViewState.userId, - action.credentials.userId)) + _viewEvents.post( + SoftLogoutViewEvents.ErrorNotSameUser( + softLogoutViewState.userId, + action.credentials.userId + ) + ) } else { setState { copy( @@ -169,7 +173,7 @@ class SoftLogoutViewModel @AssistedInject constructor( } viewModelScope.launch { try { - session.updateCredentials(action.credentials) + session.signOutService().updateCredentials(action.credentials) onSessionRestored() } catch (failure: Throwable) { _viewEvents.post(SoftLogoutViewEvents.Failure(failure)) @@ -192,7 +196,7 @@ class SoftLogoutViewModel @AssistedInject constructor( } viewModelScope.launch { try { - session.signInAgain(action.password) + session.signOutService().signInAgain(action.password) onSessionRestored() } catch (failure: Throwable) { setState { diff --git a/vector/src/main/java/im/vector/app/features/spaces/SpaceBetaHeaderItem.kt b/vector/src/main/java/im/vector/app/features/spaces/SpaceBetaHeaderItem.kt index abe7e8d2a8..667f895d4d 100644 --- a/vector/src/main/java/im/vector/app/features/spaces/SpaceBetaHeaderItem.kt +++ b/vector/src/main/java/im/vector/app/features/spaces/SpaceBetaHeaderItem.kt @@ -16,27 +16,12 @@ package im.vector.app.features.spaces -import android.view.View -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 @EpoxyModelClass(layout = R.layout.item_space_beta_header) abstract class SpaceBetaHeaderItem : VectorEpoxyModel() { - - @EpoxyAttribute(EpoxyAttribute.Option.DoNotHash) - var clickAction: ClickListener? = null - - override fun bind(holder: Holder) { - super.bind(holder) - holder.feedBackAction.onClick(clickAction) - } - - class Holder : VectorEpoxyHolder() { - val feedBackAction by bind(R.id.spaceBetaFeedbackAction) - } + class Holder : VectorEpoxyHolder() } diff --git a/vector/src/main/java/im/vector/app/features/spaces/SpaceCreationActivity.kt b/vector/src/main/java/im/vector/app/features/spaces/SpaceCreationActivity.kt index e8f3702efc..033e2ed667 100644 --- a/vector/src/main/java/im/vector/app/features/spaces/SpaceCreationActivity.kt +++ b/vector/src/main/java/im/vector/app/features/spaces/SpaceCreationActivity.kt @@ -123,7 +123,8 @@ class SpaceCreationActivity : SimpleFragmentActivity() { val frag = supportFragmentManager.findFragmentByTag(fragmentClass.name) ?: createFragment(fragmentClass) supportFragmentManager.beginTransaction() .setCustomAnimations(R.anim.fade_in, R.anim.fade_out, R.anim.fade_in, R.anim.fade_out) - .replace(views.container.id, + .replace( + views.container.id, frag, fragmentClass.name ) diff --git a/vector/src/main/java/im/vector/app/features/spaces/SpaceExploreActivity.kt b/vector/src/main/java/im/vector/app/features/spaces/SpaceExploreActivity.kt index f4610805bc..a46d38349d 100644 --- a/vector/src/main/java/im/vector/app/features/spaces/SpaceExploreActivity.kt +++ b/vector/src/main/java/im/vector/app/features/spaces/SpaceExploreActivity.kt @@ -30,7 +30,9 @@ import im.vector.app.core.extensions.registerStartForActivityResult import im.vector.app.core.extensions.replaceFragment import im.vector.app.core.platform.VectorBaseActivity import im.vector.app.databinding.ActivitySimpleBinding +import im.vector.app.features.analytics.plan.ViewRoom import im.vector.app.features.matrixto.MatrixToBottomSheet +import im.vector.app.features.matrixto.OriginOfMatrixTo import im.vector.app.features.navigation.Navigator import im.vector.app.features.roomdirectory.createroom.CreateRoomActivity import im.vector.app.features.spaces.explore.SpaceDirectoryArgs @@ -92,17 +94,23 @@ class SpaceExploreActivity : VectorBaseActivity(), Matrix finish() } is SpaceDirectoryViewEvents.NavigateToRoom -> { - navigator.openRoom(this, it.roomId) + navigator.openRoom( + context = this, + roomId = it.roomId, + trigger = ViewRoom.Trigger.SpaceHierarchy + ) } is SpaceDirectoryViewEvents.NavigateToMxToBottomSheet -> { - MatrixToBottomSheet.withLink(it.link).show(supportFragmentManager, "ShowChild") + MatrixToBottomSheet.withLink(it.link, OriginOfMatrixTo.SPACE_EXPLORE).show(supportFragmentManager, "ShowChild") } is SpaceDirectoryViewEvents.NavigateToCreateNewRoom -> { - createRoomResultLauncher.launch(CreateRoomActivity.getIntent( - this, - openAfterCreate = false, - currentSpaceId = it.currentSpaceId - )) + createRoomResultLauncher.launch( + CreateRoomActivity.getIntent( + this, + openAfterCreate = false, + currentSpaceId = it.currentSpaceId + ) + ) } } } @@ -121,8 +129,8 @@ class SpaceExploreActivity : VectorBaseActivity(), Matrix } } - override fun mxToBottomSheetNavigateToRoom(roomId: String) { - navigator.openRoom(this, roomId) + override fun mxToBottomSheetNavigateToRoom(roomId: String, trigger: ViewRoom.Trigger?) { + navigator.openRoom(this, roomId, trigger = trigger) } override fun mxToBottomSheetSwitchToSpace(spaceId: String) { diff --git a/vector/src/main/java/im/vector/app/features/spaces/SpaceListViewEvents.kt b/vector/src/main/java/im/vector/app/features/spaces/SpaceListViewEvents.kt index 582f6cd144..afc72b188d 100644 --- a/vector/src/main/java/im/vector/app/features/spaces/SpaceListViewEvents.kt +++ b/vector/src/main/java/im/vector/app/features/spaces/SpaceListViewEvents.kt @@ -19,7 +19,7 @@ package im.vector.app.features.spaces import im.vector.app.core.platform.VectorViewEvents /** - * Transient events for group list screen + * Transient events for group list screen. */ sealed class SpaceListViewEvents : VectorViewEvents { data class OpenSpace(val groupingMethodHasChanged: Boolean) : SpaceListViewEvents() diff --git a/vector/src/main/java/im/vector/app/features/spaces/SpaceListViewModel.kt b/vector/src/main/java/im/vector/app/features/spaces/SpaceListViewModel.kt index a239e939ea..52b6cc0749 100644 --- a/vector/src/main/java/im/vector/app/features/spaces/SpaceListViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/spaces/SpaceListViewModel.kt @@ -40,7 +40,6 @@ import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.distinctUntilChanged import kotlinx.coroutines.flow.flowOn import kotlinx.coroutines.flow.launchIn -import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.flow.sample import kotlinx.coroutines.launch @@ -50,6 +49,7 @@ import org.matrix.android.sdk.api.query.QueryStringValue import org.matrix.android.sdk.api.session.Session import org.matrix.android.sdk.api.session.events.model.toContent import org.matrix.android.sdk.api.session.events.model.toModel +import org.matrix.android.sdk.api.session.getRoom import org.matrix.android.sdk.api.session.group.groupSummaryQueryParams import org.matrix.android.sdk.api.session.room.RoomSortOrder import org.matrix.android.sdk.api.session.room.accountdata.RoomAccountDataTypes @@ -82,7 +82,7 @@ class SpaceListViewModel @AssistedInject constructor(@Assisted initialState: Spa init { - session.getUserLive(session.myUserId) + session.userService().getUserLive(session.myUserId) .asFlow() .setOnEach { copy( @@ -100,14 +100,14 @@ class SpaceListViewModel @AssistedInject constructor(@Assisted initialState: Spa ) } - session.getGroupSummariesLive(groupSummaryQueryParams {}) + session.groupService().getGroupSummariesLive(groupSummaryQueryParams {}) .asFlow() .setOnEach { copy(legacyGroups = it) } // XXX there should be a way to refactor this and share it - session.getPagedRoomSummariesLive( + session.roomService().getPagedRoomSummariesLive( roomSummaryQueryParams { this.memberships = listOf(Membership.JOIN) this.activeSpaceFilter = ActiveSpaceFilter.ActiveSpace(null).takeIf { @@ -120,11 +120,11 @@ class SpaceListViewModel @AssistedInject constructor(@Assisted initialState: Spa val inviteCount = if (autoAcceptInvites.hideInvites) { 0 } else { - session.getRoomSummaries( + session.roomService().getRoomSummaries( roomSummaryQueryParams { this.memberships = listOf(Membership.INVITE) } ).size } - val totalCount = session.getNotificationCountForRooms( + val totalCount = session.roomService().getNotificationCountForRooms( roomSummaryQueryParams { this.memberships = listOf(Membership.JOIN) this.activeSpaceFilter = ActiveSpaceFilter.ActiveSpace(null).takeIf { @@ -212,7 +212,8 @@ class SpaceListViewModel @AssistedInject constructor(@Assisted initialState: Spa } session.coroutineScope.launch { orderCommands.forEach { - session.getRoom(it.spaceId)?.updateAccountData(RoomAccountDataTypes.EVENT_TYPE_SPACE_ORDER, + session.getRoom(it.spaceId)?.roomAccountDataService()?.updateAccountData( + RoomAccountDataTypes.EVENT_TYPE_SPACE_ORDER, SpaceOrderContent(order = it.order).toContent() ) } @@ -278,31 +279,24 @@ class SpaceListViewModel @AssistedInject constructor(@Assisted initialState: Spa displayName = QueryStringValue.IsNotEmpty } - val flowSession = session.flow() - combine( - flowSession - .liveUser(session.myUserId) - .map { - it.getOrNull() - }, - flowSession + session.flow() .liveSpaceSummaries(params), - session - .accountDataService() + session.accountDataService() .getLiveRoomAccountDataEvents(setOf(RoomAccountDataTypes.EVENT_TYPE_SPACE_ORDER)) .asFlow() - ) { _, communityGroups, _ -> - communityGroups + ) { spaces, _ -> + spaces } .execute { async -> - val rootSpaces = session.spaceService().getRootSpaceSummaries() - val orders = rootSpaces.map { + val rootSpaces = async.invoke().orEmpty().filter { it.flattenParentIds.isEmpty() } + val orders = rootSpaces.associate { it.roomId to session.getRoom(it.roomId) + ?.roomAccountDataService() ?.getAccountDataEvent(RoomAccountDataTypes.EVENT_TYPE_SPACE_ORDER) ?.content.toModel() ?.safeOrder() - }.toMap() + } copy( asyncSpaces = async, rootSpacesOrdered = rootSpaces.sortedWith(TopLevelSpaceComparator(orders)), diff --git a/vector/src/main/java/im/vector/app/features/spaces/SpaceMenuViewModel.kt b/vector/src/main/java/im/vector/app/features/spaces/SpaceMenuViewModel.kt index cf3d01681b..4e1489ef9b 100644 --- a/vector/src/main/java/im/vector/app/features/spaces/SpaceMenuViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/spaces/SpaceMenuViewModel.kt @@ -38,6 +38,8 @@ import org.matrix.android.sdk.api.query.ActiveSpaceFilter import org.matrix.android.sdk.api.query.RoomCategoryFilter import org.matrix.android.sdk.api.session.Session import org.matrix.android.sdk.api.session.events.model.EventType +import org.matrix.android.sdk.api.session.getRoom +import org.matrix.android.sdk.api.session.getRoomSummary import org.matrix.android.sdk.api.session.room.model.Membership import org.matrix.android.sdk.api.session.room.powerlevels.PowerLevelsHelper import org.matrix.android.sdk.api.session.room.powerlevels.Role @@ -136,7 +138,7 @@ class SpaceMenuViewModel @AssistedInject constructor( } else if (state.leaveMode == SpaceMenuState.LeaveMode.LEAVE_ALL) { // need to find all child rooms that i have joined - session.getRoomSummaries( + session.roomService().getRoomSummaries( roomSummaryQueryParams { excludeType = null activeSpaceFilter = ActiveSpaceFilter.ActiveSpace(initialState.spaceId) diff --git a/vector/src/main/java/im/vector/app/features/spaces/SpaceSummaryController.kt b/vector/src/main/java/im/vector/app/features/spaces/SpaceSummaryController.kt index 95e4405da5..ca9c05f3f9 100644 --- a/vector/src/main/java/im/vector/app/features/spaces/SpaceSummaryController.kt +++ b/vector/src/main/java/im/vector/app/features/spaces/SpaceSummaryController.kt @@ -42,7 +42,8 @@ import javax.inject.Inject class SpaceSummaryController @Inject constructor( private val avatarRenderer: AvatarRenderer, private val colorProvider: ColorProvider, - private val stringProvider: StringProvider) : EpoxyController() { + private val stringProvider: StringProvider, +) : EpoxyController() { var callback: Callback? = null private var viewState: SpaceListViewState? = null @@ -62,7 +63,8 @@ class SpaceSummaryController @Inject constructor( nonNullViewState.selectedGroupingMethod, nonNullViewState.rootSpacesOrdered, nonNullViewState.expandedStates, - nonNullViewState.homeAggregateCount) + nonNullViewState.homeAggregateCount + ) if (!nonNullViewState.legacyGroups.isNullOrEmpty()) { genericFooterItem { @@ -82,8 +84,10 @@ class SpaceSummaryController @Inject constructor( avatarRenderer(host.avatarRenderer) id("all_communities") matrixItem(mxItem.copy(displayName = host.stringProvider.getString(R.string.group_all_communities))) - selected(nonNullViewState.selectedGroupingMethod is RoomGroupingMethod.ByLegacyGroup && - nonNullViewState.selectedGroupingMethod.group() == null) + selected( + nonNullViewState.selectedGroupingMethod is RoomGroupingMethod.ByLegacyGroup && + nonNullViewState.selectedGroupingMethod.group() == null + ) listener { host.callback?.onGroupSelected(null) } } } @@ -93,8 +97,10 @@ class SpaceSummaryController @Inject constructor( avatarRenderer(host.avatarRenderer) id(groupSummary.groupId) matrixItem(groupSummary.toMatrixItem()) - selected(nonNullViewState.selectedGroupingMethod is RoomGroupingMethod.ByLegacyGroup && - nonNullViewState.selectedGroupingMethod.group()?.groupId == groupSummary.groupId) + selected( + nonNullViewState.selectedGroupingMethod is RoomGroupingMethod.ByLegacyGroup && + nonNullViewState.selectedGroupingMethod.group()?.groupId == groupSummary.groupId + ) listener { host.callback?.onGroupSelected(groupSummary) } } } @@ -109,18 +115,15 @@ class SpaceSummaryController @Inject constructor( val host = this spaceBetaHeaderItem { id("beta_header") - clickAction { - host.callback?.sendFeedBack() - } } // show invites on top - - summaries?.filter { it.membership == Membership.INVITE } + summaries + ?.filter { it.membership == Membership.INVITE } ?.forEach { roomSummary -> spaceSummaryItem { avatarRenderer(host.avatarRenderer) - id(roomSummary.roomId) + id("invite_${roomSummary.roomId}") matrixItem(roomSummary.toMatrixItem()) countState(UnreadCounterBadgeView.State(1, true)) selected(false) @@ -138,33 +141,34 @@ class SpaceSummaryController @Inject constructor( } rootSpaces - ?.forEach { groupSummary -> - val isSelected = selected is RoomGroupingMethod.BySpace && groupSummary.roomId == selected.space()?.roomId + ?.filter { it.membership != Membership.INVITE } + ?.forEach { roomSummary -> + val isSelected = selected is RoomGroupingMethod.BySpace && roomSummary.roomId == selected.space()?.roomId // does it have children? - val subSpaces = groupSummary.spaceChildren?.filter { childInfo -> + val subSpaces = roomSummary.spaceChildren?.filter { childInfo -> summaries?.any { it.roomId == childInfo.childRoomId }.orFalse() }?.sortedWith(subSpaceComparator) val hasChildren = (subSpaces?.size ?: 0) > 0 - val expanded = expandedStates[groupSummary.roomId] == true + val expanded = expandedStates[roomSummary.roomId] == true spaceSummaryItem { avatarRenderer(host.avatarRenderer) - id(groupSummary.roomId) + id(roomSummary.roomId) hasChildren(hasChildren) expanded(expanded) // to debug order // matrixItem(groupSummary.copy(displayName = "${groupSummary.displayName} / ${spaceOrderInfo?.get(groupSummary.roomId)}") // .toMatrixItem()) - matrixItem(groupSummary.toMatrixItem()) + matrixItem(roomSummary.toMatrixItem()) selected(isSelected) canDrag(true) - onMore { host.callback?.onSpaceSettings(groupSummary) } - listener { host.callback?.onSpaceSelected(groupSummary) } - toggleExpand { host.callback?.onToggleExpand(groupSummary) } + onMore { host.callback?.onSpaceSettings(roomSummary) } + listener { host.callback?.onSpaceSelected(roomSummary) } + toggleExpand { host.callback?.onToggleExpand(roomSummary) } countState( UnreadCounterBadgeView.State( - groupSummary.notificationCount, - groupSummary.highlightCount > 0 + roomSummary.notificationCount, + roomSummary.highlightCount > 0 ) ) } @@ -172,7 +176,7 @@ class SpaceSummaryController @Inject constructor( if (hasChildren && expanded) { // it's expanded subSpaces?.forEach { child -> - buildSubSpace(groupSummary.roomId, summaries, expandedStates, selected, child, 1, 3) + buildSubSpace(roomSummary.roomId, summaries, expandedStates, selected, child, 1, 3) } } } diff --git a/vector/src/main/java/im/vector/app/features/spaces/SpaceSummaryItem.kt b/vector/src/main/java/im/vector/app/features/spaces/SpaceSummaryItem.kt index 6cffabd851..cc75fd5b2e 100644 --- a/vector/src/main/java/im/vector/app/features/spaces/SpaceSummaryItem.kt +++ b/vector/src/main/java/im/vector/app/features/spaces/SpaceSummaryItem.kt @@ -65,7 +65,8 @@ abstract class SpaceSummaryItem : VectorEpoxyModel() { if (hasChildren) { holder.collapseIndicator.isVisible = true holder.collapseIndicator.setImageDrawable( - ContextCompat.getDrawable(holder.view.context, + ContextCompat.getDrawable( + holder.view.context, if (expanded) R.drawable.ic_expand_less else R.drawable.ic_expand_more ) ) diff --git a/vector/src/main/java/im/vector/app/features/spaces/SubSpaceSummaryItem.kt b/vector/src/main/java/im/vector/app/features/spaces/SubSpaceSummaryItem.kt index f50d418de3..201282b113 100644 --- a/vector/src/main/java/im/vector/app/features/spaces/SubSpaceSummaryItem.kt +++ b/vector/src/main/java/im/vector/app/features/spaces/SubSpaceSummaryItem.kt @@ -57,7 +57,8 @@ abstract class SubSpaceSummaryItem : VectorEpoxyModel(), SpaceDetailEpoxyController.Listener, GalleryOrCameraDialogHelper.Listener, OnBackPressed { @@ -42,7 +44,7 @@ class CreateSpaceDetailsFragment @Inject constructor( override fun getBinding(inflater: LayoutInflater, container: ViewGroup?) = FragmentSpaceCreateGenericEpoxyFormBinding.inflate(layoutInflater, container, false) - private val galleryOrCameraDialogHelper = GalleryOrCameraDialogHelper(this, colorProvider) + private val galleryOrCameraDialogHelper = GalleryOrCameraDialogHelper(this, colorProvider, clock) override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) diff --git a/vector/src/main/java/im/vector/app/features/spaces/create/CreateSpaceState.kt b/vector/src/main/java/im/vector/app/features/spaces/create/CreateSpaceState.kt index 57782d9182..439f5491f7 100644 --- a/vector/src/main/java/im/vector/app/features/spaces/create/CreateSpaceState.kt +++ b/vector/src/main/java/im/vector/app/features/spaces/create/CreateSpaceState.kt @@ -33,9 +33,9 @@ data class CreateSpaceState( val aliasManuallyModified: Boolean = false, val aliasVerificationTask: Async = Uninitialized, val nameInlineError: String? = null, - val defaultRooms: Map? = null, - val default3pidInvite: Map? = null, - val emailValidationResult: Map? = null, + val defaultRooms: Map? = null, // Int: position in form + val default3pidInvite: Map? = null, // Int: position in form + val emailValidationResult: Map? = null, // Int: position in form val creationResult: Async = Uninitialized, val canInviteByMail: Boolean = false ) : MavericksState { diff --git a/vector/src/main/java/im/vector/app/features/spaces/create/CreateSpaceViewModel.kt b/vector/src/main/java/im/vector/app/features/spaces/create/CreateSpaceViewModel.kt index 2b8276a4d7..46da2f5826 100644 --- a/vector/src/main/java/im/vector/app/features/spaces/create/CreateSpaceViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/spaces/create/CreateSpaceViewModel.kt @@ -315,7 +315,7 @@ class CreateSpaceViewModel @AssistedInject constructor( } viewModelScope.launch { try { - when (val result = session.checkAliasAvailability(aliasLocalPart)) { + when (val result = session.roomDirectoryService().checkAliasAvailability(aliasLocalPart)) { AliasAvailabilityResult.Available -> { setState { copy( @@ -373,7 +373,7 @@ class CreateSpaceViewModel @AssistedInject constructor( ) ) when (result) { - is CreateSpaceTaskResult.Success -> { + is CreateSpaceTaskResult.Success -> { setState { copy(creationResult = Success(result.spaceId)) } diff --git a/vector/src/main/java/im/vector/app/features/spaces/create/CreateSpaceViewModelTask.kt b/vector/src/main/java/im/vector/app/features/spaces/create/CreateSpaceViewModelTask.kt index d9c18db01d..c7777649bd 100644 --- a/vector/src/main/java/im/vector/app/features/spaces/create/CreateSpaceViewModelTask.kt +++ b/vector/src/main/java/im/vector/app/features/spaces/create/CreateSpaceViewModelTask.kt @@ -113,7 +113,7 @@ class CreateSpaceViewModelTask @Inject constructor( try { val roomId = try { if (params.isPublic) { - session.createRoom( + session.roomService().createRoom( CreateRoomParams().apply { this.name = roomName this.preset = CreateRoomPreset.PRESET_PUBLIC_CHAT @@ -121,13 +121,14 @@ class CreateSpaceViewModelTask @Inject constructor( ) } else { val homeServerCapabilities = session + .homeServerCapabilitiesService() .getHomeServerCapabilities() val restrictedSupport = homeServerCapabilities .isFeatureSupported(HomeServerCapabilities.ROOM_CAP_RESTRICTED) val createRestricted = restrictedSupport == HomeServerCapabilities.RoomCapabilitySupport.SUPPORTED if (createRestricted) { - session.createRoom(CreateRoomParams().apply { + session.roomService().createRoom(CreateRoomParams().apply { this.name = roomName this.featurePreset = RestrictedRoomPreset( homeServerCapabilities, @@ -140,7 +141,7 @@ class CreateSpaceViewModelTask @Inject constructor( } }) } else { - session.createRoom(CreateRoomParams().apply { + session.roomService().createRoom(CreateRoomParams().apply { this.name = roomName visibility = RoomDirectoryVisibility.PRIVATE this.preset = CreateRoomPreset.PRESET_PRIVATE_CHAT diff --git a/vector/src/main/java/im/vector/app/features/spaces/create/SpaceDetailEpoxyController.kt b/vector/src/main/java/im/vector/app/features/spaces/create/SpaceDetailEpoxyController.kt index b25ae4f2c7..aee405388b 100644 --- a/vector/src/main/java/im/vector/app/features/spaces/create/SpaceDetailEpoxyController.kt +++ b/vector/src/main/java/im/vector/app/features/spaces/create/SpaceDetailEpoxyController.kt @@ -102,7 +102,8 @@ class SpaceDetailEpoxyController @Inject constructor( } errorMessage( host.roomAliasErrorFormatter.format( - (((data.aliasVerificationTask as? Fail)?.error) as? RoomAliasError)) + (((data.aliasVerificationTask as? Fail)?.error) as? RoomAliasError) + ) ) onTextChange(host.aliasTextWatcher) } diff --git a/vector/src/main/java/im/vector/app/features/spaces/create/WizardButtonView.kt b/vector/src/main/java/im/vector/app/features/spaces/create/WizardButtonView.kt index 971d61dd9c..c1bfba7205 100644 --- a/vector/src/main/java/im/vector/app/features/spaces/create/WizardButtonView.kt +++ b/vector/src/main/java/im/vector/app/features/spaces/create/WizardButtonView.kt @@ -30,7 +30,7 @@ import im.vector.app.core.extensions.setTextOrHide import im.vector.app.databinding.ViewSpaceTypeButtonBinding class WizardButtonView @JvmOverloads constructor(context: Context, attrs: AttributeSet? = null, defStyle: Int = 0) : - ConstraintLayout(context, attrs, defStyle) { + ConstraintLayout(context, attrs, defStyle) { private val views: ViewSpaceTypeButtonBinding diff --git a/vector/src/main/java/im/vector/app/features/spaces/explore/SpaceDirectoryController.kt b/vector/src/main/java/im/vector/app/features/spaces/explore/SpaceDirectoryController.kt index a2c1380098..9249240d5c 100644 --- a/vector/src/main/java/im/vector/app/features/spaces/explore/SpaceDirectoryController.kt +++ b/vector/src/main/java/im/vector/app/features/spaces/explore/SpaceDirectoryController.kt @@ -36,6 +36,7 @@ import im.vector.app.features.home.AvatarRenderer import im.vector.app.features.home.room.list.spaceChildInfoItem import im.vector.lib.core.utils.epoxy.charsequence.toEpoxyCharSequence import me.gujun.android.span.span +import org.matrix.android.sdk.api.extensions.orFalse import org.matrix.android.sdk.api.failure.Failure import org.matrix.android.sdk.api.failure.MatrixError.Companion.M_UNRECOGNIZED import org.matrix.android.sdk.api.session.room.members.ChangeMembershipState @@ -145,6 +146,7 @@ class SpaceDirectoryController @Inject constructor( matrixItem(matrixItem) avatarRenderer(host.avatarRenderer) topic(info.topic) + suggested(info.suggested.orFalse()) errorLabel( error?.let { host.stringProvider.getString(R.string.error_failed_to_join_room, host.errorFormatter.toHumanReadable(it)) diff --git a/vector/src/main/java/im/vector/app/features/spaces/explore/SpaceDirectoryFragment.kt b/vector/src/main/java/im/vector/app/features/spaces/explore/SpaceDirectoryFragment.kt index 7e7b1d8996..e59087778f 100644 --- a/vector/src/main/java/im/vector/app/features/spaces/explore/SpaceDirectoryFragment.kt +++ b/vector/src/main/java/im/vector/app/features/spaces/explore/SpaceDirectoryFragment.kt @@ -169,7 +169,7 @@ class SpaceDirectoryFragment @Inject constructor( } else { toolbar?.title = state.currentRootSummary?.name ?: state.currentRootSummary?.canonicalAlias - ?: getString(R.string.space_explore_activity_title) + ?: getString(R.string.space_explore_activity_title) } spaceCardRenderer.render(state.currentRootSummary, emptyList(), this, views.spaceCard, showDescription = false) diff --git a/vector/src/main/java/im/vector/app/features/spaces/explore/SpaceDirectoryViewModel.kt b/vector/src/main/java/im/vector/app/features/spaces/explore/SpaceDirectoryViewModel.kt index abc70ccbc1..2ddcb42e2a 100644 --- a/vector/src/main/java/im/vector/app/features/spaces/explore/SpaceDirectoryViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/spaces/explore/SpaceDirectoryViewModel.kt @@ -27,6 +27,9 @@ import dagger.assisted.AssistedInject import im.vector.app.core.di.MavericksAssistedViewModelFactory import im.vector.app.core.di.hiltMavericksViewModelFactory import im.vector.app.core.platform.VectorViewModel +import im.vector.app.features.analytics.AnalyticsTracker +import im.vector.app.features.analytics.extensions.toAnalyticsJoinedRoom +import im.vector.app.features.analytics.plan.JoinedRoom import im.vector.app.features.powerlevel.PowerLevelsFlowFactory import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.flow.launchIn @@ -35,6 +38,8 @@ import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.launch import org.matrix.android.sdk.api.session.Session import org.matrix.android.sdk.api.session.events.model.EventType +import org.matrix.android.sdk.api.session.getRoom +import org.matrix.android.sdk.api.session.getRoomSummary import org.matrix.android.sdk.api.session.room.model.Membership import org.matrix.android.sdk.api.session.room.model.RoomJoinRules import org.matrix.android.sdk.api.session.room.model.RoomSummary @@ -47,7 +52,8 @@ import timber.log.Timber class SpaceDirectoryViewModel @AssistedInject constructor( @Assisted val initialState: SpaceDirectoryState, - private val session: Session + private val session: Session, + private val analyticsTracker: AnalyticsTracker ) : VectorViewModel(initialState) { @AssistedFactory @@ -105,8 +111,12 @@ class SpaceDirectoryViewModel @AssistedInject constructor( .onEach { val powerLevelsHelper = PowerLevelsHelper(it) setState { - copy(canAddRooms = powerLevelsHelper.isUserAllowedToSend(session.myUserId, true, - EventType.STATE_SPACE_CHILD)) + copy( + canAddRooms = powerLevelsHelper.isUserAllowedToSend( + session.myUserId, true, + EventType.STATE_SPACE_CHILD + ) + ) } } .launchIn(viewModelScope) @@ -275,9 +285,9 @@ class SpaceDirectoryViewModel @AssistedInject constructor( knownSummaries = ( knownSummaries + (paginate.children.mapNotNull { - session.getRoomSummary(it.childRoomId) - ?.takeIf { it.membership == Membership.JOIN } // only take if joined because it will be up to date (synced) - }) + session.getRoomSummary(it.childRoomId) + ?.takeIf { it.membership == Membership.JOIN } // only take if joined because it will be up to date (synced) + }) ).distinctBy { it.roomId } query = query.copy( @@ -409,11 +419,14 @@ class SpaceDirectoryViewModel @AssistedInject constructor( if (isSpace) { session.spaceService().joinSpace(childId, null, spaceChildInfo.viaServers) } else { - session.joinRoom(childId, null, spaceChildInfo.viaServers) + session.roomService().joinRoom(childId, null, spaceChildInfo.viaServers) } } catch (failure: Throwable) { Timber.e(failure, "## Space: Failed to join room or subspace") } + + session.getRoomSummary(childId) + ?.let { analyticsTracker.capture(it.toAnalyticsJoinedRoom(JoinedRoom.Trigger.SpaceHierarchy)) } } } } diff --git a/vector/src/main/java/im/vector/app/features/spaces/invite/SpaceInviteBottomSheetViewModel.kt b/vector/src/main/java/im/vector/app/features/spaces/invite/SpaceInviteBottomSheetViewModel.kt index b03d21e6bd..5e56373a88 100644 --- a/vector/src/main/java/im/vector/app/features/spaces/invite/SpaceInviteBottomSheetViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/spaces/invite/SpaceInviteBottomSheetViewModel.kt @@ -33,6 +33,8 @@ import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch import org.matrix.android.sdk.api.extensions.tryOrNull import org.matrix.android.sdk.api.session.Session +import org.matrix.android.sdk.api.session.getRoomSummary +import org.matrix.android.sdk.api.session.getUser import org.matrix.android.sdk.api.session.room.model.Membership import org.matrix.android.sdk.api.session.room.model.RoomSummary import org.matrix.android.sdk.api.session.room.peeking.PeekResult @@ -46,7 +48,7 @@ class SpaceInviteBottomSheetViewModel @AssistedInject constructor( init { session.getRoomSummary(initialState.spaceId)?.let { roomSummary -> val knownMembers = roomSummary.otherMemberIds.filter { - session.getExistingDirectRoomWithUser(it) != null + session.roomService().getExistingDirectRoomWithUser(it) != null }.mapNotNull { session.getUser(it) } // put one with avatar first, and take 5 val peopleYouKnow = (knownMembers.filter { it.avatarUrl != null } + knownMembers.filter { it.avatarUrl == null }) @@ -66,11 +68,11 @@ class SpaceInviteBottomSheetViewModel @AssistedInject constructor( } /** - * Try to request the room summary api to get more info + * Try to request the room summary api to get more info. */ private fun getLatestRoomSummary(roomSummary: RoomSummary) { viewModelScope.launch(Dispatchers.IO) { - val peekResult = tryOrNull { session.peekRoom(roomSummary.roomId) } as? PeekResult.Success ?: return@launch + val peekResult = tryOrNull { session.roomService().peekRoom(roomSummary.roomId) } as? PeekResult.Success ?: return@launch setState { copy( summary = Success( diff --git a/vector/src/main/java/im/vector/app/features/spaces/leave/SpaceLeaveAdvancedViewModel.kt b/vector/src/main/java/im/vector/app/features/spaces/leave/SpaceLeaveAdvancedViewModel.kt index fc241e711c..3f5a27f696 100644 --- a/vector/src/main/java/im/vector/app/features/spaces/leave/SpaceLeaveAdvancedViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/spaces/leave/SpaceLeaveAdvancedViewModel.kt @@ -36,6 +36,8 @@ import okhttp3.internal.toImmutableList import org.matrix.android.sdk.api.query.ActiveSpaceFilter import org.matrix.android.sdk.api.query.RoomCategoryFilter import org.matrix.android.sdk.api.session.Session +import org.matrix.android.sdk.api.session.getRoom +import org.matrix.android.sdk.api.session.getRoomSummary import org.matrix.android.sdk.api.session.room.model.Membership import org.matrix.android.sdk.api.session.room.roomSummaryQueryParams import org.matrix.android.sdk.flow.flow @@ -72,7 +74,7 @@ class SpaceLeaveAdvancedViewModel @AssistedInject constructor( try { state.selectedRooms.forEach { try { - session.leaveRoom(it) + session.roomService().leaveRoom(it) } catch (failure: Throwable) { // silently ignore? Timber.e(failure, "Fail to leave sub rooms/spaces") @@ -110,7 +112,7 @@ class SpaceLeaveAdvancedViewModel @AssistedInject constructor( } viewModelScope.launch { - val children = session.getRoomSummaries( + val children = session.roomService().getRoomSummaries( roomSummaryQueryParams { includeType = null memberships = listOf(Membership.JOIN) diff --git a/vector/src/main/java/im/vector/app/features/spaces/manage/SpaceAddRoomsViewModel.kt b/vector/src/main/java/im/vector/app/features/spaces/manage/SpaceAddRoomsViewModel.kt index 318f701985..0be0fb0464 100644 --- a/vector/src/main/java/im/vector/app/features/spaces/manage/SpaceAddRoomsViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/spaces/manage/SpaceAddRoomsViewModel.kt @@ -39,6 +39,7 @@ import org.matrix.android.sdk.api.query.ActiveSpaceFilter import org.matrix.android.sdk.api.query.QueryStringValue import org.matrix.android.sdk.api.query.RoomCategoryFilter import org.matrix.android.sdk.api.session.Session +import org.matrix.android.sdk.api.session.getRoomSummary import org.matrix.android.sdk.api.session.room.RoomSortOrder import org.matrix.android.sdk.api.session.room.UpdatableLivePageResult import org.matrix.android.sdk.api.session.room.model.Membership @@ -63,8 +64,10 @@ class SpaceAddRoomsViewModel @AssistedInject constructor( companion object : MavericksViewModelFactory by hiltMavericksViewModelFactory() + private val roomService = session.roomService() + val spaceUpdatableLivePageResult: UpdatableLivePageResult by lazy { - session.getFilteredPagedRoomSummariesLive( + roomService.getFilteredPagedRoomSummariesLive( roomSummaryQueryParams { this.memberships = listOf(Membership.JOIN) this.excludeType = null @@ -84,12 +87,12 @@ class SpaceAddRoomsViewModel @AssistedInject constructor( val spaceCountFlow: Flow by lazy { spaceUpdatableLivePageResult.livePagedList.asFlow() - .flatMapLatest { session.getRoomCountLive(spaceUpdatableLivePageResult.queryParams).asFlow() } + .flatMapLatest { roomService.getRoomCountLive(spaceUpdatableLivePageResult.queryParams).asFlow() } .distinctUntilChanged() } val roomUpdatableLivePageResult: UpdatableLivePageResult by lazy { - session.getFilteredPagedRoomSummariesLive( + roomService.getFilteredPagedRoomSummariesLive( roomSummaryQueryParams { this.memberships = listOf(Membership.JOIN) this.excludeType = listOf(RoomType.SPACE) @@ -110,12 +113,12 @@ class SpaceAddRoomsViewModel @AssistedInject constructor( val roomCountFlow: Flow by lazy { roomUpdatableLivePageResult.livePagedList.asFlow() - .flatMapLatest { session.getRoomCountLive(roomUpdatableLivePageResult.queryParams).asFlow() } + .flatMapLatest { roomService.getRoomCountLive(roomUpdatableLivePageResult.queryParams).asFlow() } .distinctUntilChanged() } val dmUpdatableLivePageResult: UpdatableLivePageResult by lazy { - session.getFilteredPagedRoomSummariesLive( + roomService.getFilteredPagedRoomSummariesLive( roomSummaryQueryParams { this.memberships = listOf(Membership.JOIN) this.excludeType = listOf(RoomType.SPACE) @@ -136,7 +139,7 @@ class SpaceAddRoomsViewModel @AssistedInject constructor( val dmCountFlow: Flow by lazy { dmUpdatableLivePageResult.livePagedList.asFlow() - .flatMapLatest { session.getRoomCountLive(dmUpdatableLivePageResult.queryParams).asFlow() } + .flatMapLatest { roomService.getRoomCountLive(dmUpdatableLivePageResult.queryParams).asFlow() } .distinctUntilChanged() } diff --git a/vector/src/main/java/im/vector/app/features/spaces/manage/SpaceManageRoomViewEvents.kt b/vector/src/main/java/im/vector/app/features/spaces/manage/SpaceManageRoomViewEvents.kt index 8ba7398a2f..043c71f549 100644 --- a/vector/src/main/java/im/vector/app/features/spaces/manage/SpaceManageRoomViewEvents.kt +++ b/vector/src/main/java/im/vector/app/features/spaces/manage/SpaceManageRoomViewEvents.kt @@ -19,6 +19,6 @@ package im.vector.app.features.spaces.manage import im.vector.app.core.platform.VectorViewEvents sealed class SpaceManageRoomViewEvents : VectorViewEvents { -// object BulkActionSuccess: SpaceManageRoomViewEvents() + // object BulkActionSuccess: SpaceManageRoomViewEvents() data class BulkActionFailure(val errorList: List) : SpaceManageRoomViewEvents() } diff --git a/vector/src/main/java/im/vector/app/features/spaces/manage/SpaceManageRoomsViewModel.kt b/vector/src/main/java/im/vector/app/features/spaces/manage/SpaceManageRoomsViewModel.kt index a1dd26a936..6896b85924 100644 --- a/vector/src/main/java/im/vector/app/features/spaces/manage/SpaceManageRoomsViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/spaces/manage/SpaceManageRoomsViewModel.kt @@ -33,6 +33,7 @@ import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch import org.matrix.android.sdk.api.extensions.tryOrNull import org.matrix.android.sdk.api.session.Session +import org.matrix.android.sdk.api.session.getRoomSummary class SpaceManageRoomsViewModel @AssistedInject constructor( @Assisted val initialState: SpaceManageRoomViewState, diff --git a/vector/src/main/java/im/vector/app/features/spaces/manage/SpaceManageViewState.kt b/vector/src/main/java/im/vector/app/features/spaces/manage/SpaceManageViewState.kt index 82abc823c3..0baa4d309f 100644 --- a/vector/src/main/java/im/vector/app/features/spaces/manage/SpaceManageViewState.kt +++ b/vector/src/main/java/im/vector/app/features/spaces/manage/SpaceManageViewState.kt @@ -24,6 +24,7 @@ enum class ManageType { Settings, ManageRooms } + data class SpaceManageViewState( val spaceId: String = "", val manageType: ManageType diff --git a/vector/src/main/java/im/vector/app/features/spaces/manage/SpaceSettingsController.kt b/vector/src/main/java/im/vector/app/features/spaces/manage/SpaceSettingsController.kt index 7cd5a70279..afe5200fbb 100644 --- a/vector/src/main/java/im/vector/app/features/spaces/manage/SpaceSettingsController.kt +++ b/vector/src/main/java/im/vector/app/features/spaces/manage/SpaceSettingsController.kt @@ -65,13 +65,13 @@ class SpaceSettingsController @Inject constructor( id("avatar") enabled(data.actionPermissions.canChangeAvatar) when (val avatarAction = data.avatarAction) { - RoomSettingsViewState.AvatarAction.None -> { + RoomSettingsViewState.AvatarAction.None -> { // Use the current value avatarRenderer(host.avatarRenderer) // We do not want to use the fallback avatar url, which can be the other user avatar, or the current user avatar. matrixItem(roomSummary.toMatrixItem().updateAvatar(data.currentRoomAvatarUrl)) } - RoomSettingsViewState.AvatarAction.DeleteAvatar -> + RoomSettingsViewState.AvatarAction.DeleteAvatar -> imageUri(null) is RoomSettingsViewState.AvatarAction.UpdateAvatar -> imageUri(avatarAction.newAvatarUri) diff --git a/vector/src/main/java/im/vector/app/features/spaces/manage/SpaceSettingsFragment.kt b/vector/src/main/java/im/vector/app/features/spaces/manage/SpaceSettingsFragment.kt index db9420abc2..0264fdabfc 100644 --- a/vector/src/main/java/im/vector/app/features/spaces/manage/SpaceSettingsFragment.kt +++ b/vector/src/main/java/im/vector/app/features/spaces/manage/SpaceSettingsFragment.kt @@ -38,7 +38,7 @@ import im.vector.app.core.intent.getFilenameFromUri import im.vector.app.core.platform.OnBackPressed import im.vector.app.core.platform.VectorBaseFragment import im.vector.app.core.resources.ColorProvider -import im.vector.app.core.resources.DrawableProvider +import im.vector.app.core.time.Clock import im.vector.app.core.utils.toast import im.vector.app.databinding.FragmentRoomSettingGenericBinding import im.vector.app.features.home.AvatarRenderer @@ -60,9 +60,9 @@ import javax.inject.Inject class SpaceSettingsFragment @Inject constructor( private val epoxyController: SpaceSettingsController, - private val colorProvider: ColorProvider, + colorProvider: ColorProvider, + clock: Clock, private val avatarRenderer: AvatarRenderer, - private val drawableProvider: DrawableProvider ) : VectorBaseFragment(), SpaceSettingsController.Callback, GalleryOrCameraDialogHelper.Listener, @@ -73,7 +73,7 @@ class SpaceSettingsFragment @Inject constructor( private lateinit var roomJoinRuleSharedActionViewModel: RoomJoinRuleSharedActionViewModel - private val galleryOrCameraDialogHelper = GalleryOrCameraDialogHelper(this, colorProvider) + private val galleryOrCameraDialogHelper = GalleryOrCameraDialogHelper(this, colorProvider, clock) override fun getBinding(inflater: LayoutInflater, container: ViewGroup?) = FragmentRoomSettingGenericBinding.inflate(inflater) @@ -96,8 +96,8 @@ class SpaceSettingsFragment @Inject constructor( viewModel.observeViewEvents { when (it) { is RoomSettingsViewEvents.Failure -> showFailure(it.throwable) - RoomSettingsViewEvents.Success -> showSuccess() - RoomSettingsViewEvents.GoBack -> { + RoomSettingsViewEvents.Success -> showSuccess() + RoomSettingsViewEvents.GoBack -> { ignoreChanges = true vectorBaseActivity.onBackPressed() } @@ -236,7 +236,8 @@ class SpaceSettingsFragment @Inject constructor( RoomSettingsAction.SetAvatarAction( RoomSettingsViewState.AvatarAction.UpdateAvatar( newAvatarUri = uri, - newAvatarFileName = getFilenameFromUri(requireContext(), uri) ?: UUID.randomUUID().toString()) + newAvatarFileName = getFilenameFromUri(requireContext(), uri) ?: UUID.randomUUID().toString() + ) ) ) } @@ -244,10 +245,10 @@ class SpaceSettingsFragment @Inject constructor( override fun onAvatarDelete() { withState(viewModel) { when (it.avatarAction) { - RoomSettingsViewState.AvatarAction.None -> { + RoomSettingsViewState.AvatarAction.None -> { viewModel.handle(RoomSettingsAction.SetAvatarAction(RoomSettingsViewState.AvatarAction.DeleteAvatar)) } - RoomSettingsViewState.AvatarAction.DeleteAvatar -> { + RoomSettingsViewState.AvatarAction.DeleteAvatar -> { /* Should not happen */ } is RoomSettingsViewState.AvatarAction.UpdateAvatar -> { diff --git a/vector/src/main/java/im/vector/app/features/spaces/people/SpacePeopleActivity.kt b/vector/src/main/java/im/vector/app/features/spaces/people/SpacePeopleActivity.kt index 23a76b4b68..09a0099fb7 100644 --- a/vector/src/main/java/im/vector/app/features/spaces/people/SpacePeopleActivity.kt +++ b/vector/src/main/java/im/vector/app/features/spaces/people/SpacePeopleActivity.kt @@ -30,6 +30,7 @@ import im.vector.app.core.extensions.replaceFragment import im.vector.app.core.platform.GenericIdArgs import im.vector.app.core.platform.VectorBaseActivity import im.vector.app.databinding.ActivitySimpleLoadingBinding +import im.vector.app.features.analytics.plan.ViewRoom import im.vector.app.features.spaces.share.ShareSpaceBottomSheet import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.onEach @@ -90,7 +91,11 @@ class SpacePeopleActivity : VectorBaseActivity() { } private fun navigateToRooms(action: SpacePeopleSharedAction.NavigateToRoom) { - navigator.openRoom(this, action.roomId) + navigator.openRoom( + context = this, + roomId = action.roomId, + trigger = ViewRoom.Trigger.MobileSpaceMembers + ) finish() } diff --git a/vector/src/main/java/im/vector/app/features/spaces/people/SpacePeopleViewModel.kt b/vector/src/main/java/im/vector/app/features/spaces/people/SpacePeopleViewModel.kt index 4fee675351..988f2214c9 100644 --- a/vector/src/main/java/im/vector/app/features/spaces/people/SpacePeopleViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/spaces/people/SpacePeopleViewModel.kt @@ -65,7 +65,7 @@ class SpacePeopleViewModel @AssistedInject constructor( private fun handleChatWith(action: SpacePeopleViewAction.ChatWith) { val otherUserId = action.member.userId if (otherUserId == session.myUserId) return - val existingRoomId = session.getExistingDirectRoomWithUser(otherUserId) + val existingRoomId = session.roomService().getExistingDirectRoomWithUser(otherUserId) if (existingRoomId != null) { // just open it _viewEvents.post(SpacePeopleViewEvents.OpenRoom(existingRoomId)) @@ -86,7 +86,7 @@ class SpacePeopleViewModel @AssistedInject constructor( } try { - val roomId = session.createRoom(roomParams) + val roomId = session.roomService().createRoom(roomParams) analyticsTracker.capture(CreatedRoom(isDM = roomParams.isDirect.orFalse())) _viewEvents.post(SpacePeopleViewEvents.OpenRoom(roomId)) setState { copy(createAndInviteState = Success(roomId)) } diff --git a/vector/src/main/java/im/vector/app/features/spaces/preview/SpacePreviewFragment.kt b/vector/src/main/java/im/vector/app/features/spaces/preview/SpacePreviewFragment.kt index e97dab1d86..271b00c707 100644 --- a/vector/src/main/java/im/vector/app/features/spaces/preview/SpacePreviewFragment.kt +++ b/vector/src/main/java/im/vector/app/features/spaces/preview/SpacePreviewFragment.kt @@ -105,7 +105,7 @@ class SpacePreviewFragment @Inject constructor( views.spacePreviewAcceptInviteButton.isEnabled = false views.spacePreviewDeclineInviteButton.isEnabled = false } - is Fail -> { + is Fail -> { views.spacePreviewPeekingProgress.isVisible = false views.spacePreviewButtonBar.isVisible = false } @@ -127,10 +127,10 @@ class SpacePreviewFragment @Inject constructor( private fun handleViewEvents(viewEvents: SpacePreviewViewEvents) { when (viewEvents) { - SpacePreviewViewEvents.Dismiss -> { + SpacePreviewViewEvents.Dismiss -> { sharedActionViewModel.post(SpacePreviewSharedAction.DismissAction) } - SpacePreviewViewEvents.JoinSuccess -> { + SpacePreviewViewEvents.JoinSuccess -> { sharedActionViewModel.post(SpacePreviewSharedAction.HideModalLoading) sharedActionViewModel.post(SpacePreviewSharedAction.DismissAction) } diff --git a/vector/src/main/java/im/vector/app/features/spaces/preview/SpacePreviewState.kt b/vector/src/main/java/im/vector/app/features/spaces/preview/SpacePreviewState.kt index 14f9a45d68..ca6a472985 100644 --- a/vector/src/main/java/im/vector/app/features/spaces/preview/SpacePreviewState.kt +++ b/vector/src/main/java/im/vector/app/features/spaces/preview/SpacePreviewState.kt @@ -22,8 +22,8 @@ import com.airbnb.mvrx.Uninitialized data class SpacePreviewState( val idOrAlias: String, - val name: String? = null, - val topic: String? = null, + val name: String? = null, + val topic: String? = null, val avatarUrl: String? = null, val spaceInfo: Async = Uninitialized, val childInfoList: Async> = Uninitialized, diff --git a/vector/src/main/java/im/vector/app/features/spaces/preview/SpacePreviewViewModel.kt b/vector/src/main/java/im/vector/app/features/spaces/preview/SpacePreviewViewModel.kt index 149f582c5c..888f4a65b5 100644 --- a/vector/src/main/java/im/vector/app/features/spaces/preview/SpacePreviewViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/spaces/preview/SpacePreviewViewModel.kt @@ -31,6 +31,7 @@ import im.vector.app.core.platform.VectorViewModel import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch import org.matrix.android.sdk.api.session.Session +import org.matrix.android.sdk.api.session.getRoomSummary import org.matrix.android.sdk.api.session.room.model.RoomType import org.matrix.android.sdk.api.session.room.peeking.PeekResult import org.matrix.android.sdk.api.session.space.JoinSpaceResult @@ -64,8 +65,8 @@ class SpacePreviewViewModel @AssistedInject constructor( override fun handle(action: SpacePreviewViewAction) { when (action) { - SpacePreviewViewAction.ViewReady -> handleReady() - SpacePreviewViewAction.AcceptInvite -> handleAcceptInvite() + SpacePreviewViewAction.ViewReady -> handleReady() + SpacePreviewViewAction.AcceptInvite -> handleAcceptInvite() SpacePreviewViewAction.DismissInvite -> handleDismissInvite() } } @@ -103,7 +104,7 @@ class SpacePreviewViewModel @AssistedInject constructor( // For now we don't handle partial success, it's just success _viewEvents.post(SpacePreviewViewEvents.JoinSuccess) } - is JoinSpaceResult.Fail -> { + is JoinSpaceResult.Fail -> { _viewEvents.post(SpacePreviewViewEvents.JoinFailure(errorFormatter.toHumanReadable(joinResult.error))) } } diff --git a/vector/src/main/java/im/vector/app/features/spaces/preview/SpaceTabView.kt b/vector/src/main/java/im/vector/app/features/spaces/preview/SpaceTabView.kt index 41fc8bf6b9..ea28129262 100644 --- a/vector/src/main/java/im/vector/app/features/spaces/preview/SpaceTabView.kt +++ b/vector/src/main/java/im/vector/app/features/spaces/preview/SpaceTabView.kt @@ -24,7 +24,7 @@ import im.vector.app.R class SpaceTabView constructor(context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0) : - LinearLayout(context, attrs, defStyleAttr) { + LinearLayout(context, attrs, defStyleAttr) { constructor(context: Context, attrs: AttributeSet) : this(context, attrs, 0) {} constructor(context: Context) : this(context, null, 0) {} diff --git a/vector/src/main/java/im/vector/app/features/spaces/share/ShareSpaceViewModel.kt b/vector/src/main/java/im/vector/app/features/spaces/share/ShareSpaceViewModel.kt index c624f1ed46..43d69e6fef 100644 --- a/vector/src/main/java/im/vector/app/features/spaces/share/ShareSpaceViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/spaces/share/ShareSpaceViewModel.kt @@ -30,6 +30,8 @@ import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.onEach import org.matrix.android.sdk.api.extensions.orFalse import org.matrix.android.sdk.api.session.Session +import org.matrix.android.sdk.api.session.getRoom +import org.matrix.android.sdk.api.session.getRoomSummary import org.matrix.android.sdk.api.session.room.powerlevels.PowerLevelsHelper class ShareSpaceViewModel @AssistedInject constructor( diff --git a/vector/src/main/java/im/vector/app/features/sync/widget/SyncStateView.kt b/vector/src/main/java/im/vector/app/features/sync/widget/SyncStateView.kt index ce6df67d53..27116093d2 100755 --- a/vector/src/main/java/im/vector/app/features/sync/widget/SyncStateView.kt +++ b/vector/src/main/java/im/vector/app/features/sync/widget/SyncStateView.kt @@ -28,7 +28,7 @@ import org.matrix.android.sdk.api.session.initsync.SyncStatusService import org.matrix.android.sdk.api.session.sync.SyncState class SyncStateView @JvmOverloads constructor(context: Context, attrs: AttributeSet? = null, defStyle: Int = 0) : - LinearLayout(context, attrs, defStyle) { + LinearLayout(context, attrs, defStyle) { private val views: ViewSyncStateBinding @@ -54,7 +54,7 @@ class SyncStateView @JvmOverloads constructor(context: Context, attrs: Attribute views.syncStateProgressBar.isVisible = newState is SyncState.Running && newState.afterPause if (newState == SyncState.NoNetwork) { - val isAirplaneModeOn = isAirplaneModeOn(context) + val isAirplaneModeOn = context.isAirplaneModeOn() views.syncStateNoNetwork.isVisible = isAirplaneModeOn.not() views.syncStateNoNetworkAirplane.isVisible = isAirplaneModeOn } else { diff --git a/vector/src/main/java/im/vector/app/features/terms/ReviewTermsViewModel.kt b/vector/src/main/java/im/vector/app/features/terms/ReviewTermsViewModel.kt index 8fe1f598f6..28febdac37 100644 --- a/vector/src/main/java/im/vector/app/features/terms/ReviewTermsViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/terms/ReviewTermsViewModel.kt @@ -84,7 +84,7 @@ class ReviewTermsViewModel @AssistedInject constructor( viewModelScope.launch { try { - session.agreeToTerms( + session.termsService().agreeToTerms( termsArgs.type, termsArgs.baseURL, agreedUrls, @@ -109,9 +109,10 @@ class ReviewTermsViewModel @AssistedInject constructor( viewModelScope.launch { try { - val data = session.getTerms(termsArgs.type, termsArgs.baseURL) + val data = session.termsService().getTerms(termsArgs.type, termsArgs.baseURL) val terms = data.serverResponse.getLocalizedTerms(action.preferredLanguageCode).map { - Term(it.localizedUrl ?: "", + Term( + it.localizedUrl ?: "", it.localizedName ?: "", it.version, accepted = data.alreadyAcceptedTermUrls.contains(it.localizedUrl) diff --git a/vector/src/main/java/im/vector/app/features/themes/ThemeUtils.kt b/vector/src/main/java/im/vector/app/features/themes/ThemeUtils.kt index 6c8ea0a3f9..3b1e8240fa 100644 --- a/vector/src/main/java/im/vector/app/features/themes/ThemeUtils.kt +++ b/vector/src/main/java/im/vector/app/features/themes/ThemeUtils.kt @@ -76,7 +76,7 @@ object ThemeUtils { } /** - * Provides the selected application theme + * Provides the selected application theme. * * @param context the context * @return the selected application theme @@ -106,7 +106,7 @@ object ThemeUtils { } /** - * Update the application theme + * Update the application theme. * * @param aTheme the new theme */ @@ -141,7 +141,7 @@ object ThemeUtils { } /** - * Translates color attributes to colors + * Translates color attributes to colors. * * @param c Context * @param colorAttribute Color Attribute @@ -173,7 +173,7 @@ object ThemeUtils { } /** - * Tint the drawable with a theme attribute + * Tint the drawable with a theme attribute. * * @param context the context * @param drawable the drawable to tint @@ -185,7 +185,7 @@ object ThemeUtils { } /** - * Tint the drawable with a color integer + * Tint the drawable with a color integer. * * @param drawable the drawable to tint * @param color the color diff --git a/vector/src/main/java/im/vector/app/features/ui/SharedPreferencesUiStateRepository.kt b/vector/src/main/java/im/vector/app/features/ui/SharedPreferencesUiStateRepository.kt index e46c3516ca..050232dd84 100644 --- a/vector/src/main/java/im/vector/app/features/ui/SharedPreferencesUiStateRepository.kt +++ b/vector/src/main/java/im/vector/app/features/ui/SharedPreferencesUiStateRepository.kt @@ -23,7 +23,7 @@ import im.vector.app.features.settings.VectorPreferences import javax.inject.Inject /** - * This class is used to persist UI state across application restart + * This class is used to persist UI state across application restart. */ class SharedPreferencesUiStateRepository @Inject constructor( private val sharedPreferences: SharedPreferences, @@ -50,12 +50,14 @@ class SharedPreferencesUiStateRepository @Inject constructor( override fun storeDisplayMode(displayMode: RoomListDisplayMode) { sharedPreferences.edit { - putInt(KEY_DISPLAY_MODE, + putInt( + KEY_DISPLAY_MODE, when (displayMode) { RoomListDisplayMode.PEOPLE -> VALUE_DISPLAY_MODE_PEOPLE RoomListDisplayMode.ROOMS -> VALUE_DISPLAY_MODE_ROOMS else -> VALUE_DISPLAY_MODE_CATCHUP - }) + } + ) } } diff --git a/vector/src/main/java/im/vector/app/features/ui/UiStateRepository.kt b/vector/src/main/java/im/vector/app/features/ui/UiStateRepository.kt index 3c48f8972d..e2a87ce226 100644 --- a/vector/src/main/java/im/vector/app/features/ui/UiStateRepository.kt +++ b/vector/src/main/java/im/vector/app/features/ui/UiStateRepository.kt @@ -19,12 +19,12 @@ package im.vector.app.features.ui import im.vector.app.features.home.RoomListDisplayMode /** - * This interface is used to persist UI state across application restart + * This interface is used to persist UI state across application restart. */ interface UiStateRepository { /** - * Reset all the saved data + * Reset all the saved data. */ fun reset() diff --git a/vector/src/main/java/im/vector/app/features/usercode/UserCodeActivity.kt b/vector/src/main/java/im/vector/app/features/usercode/UserCodeActivity.kt index 9e0aa15297..aab83b48a0 100644 --- a/vector/src/main/java/im/vector/app/features/usercode/UserCodeActivity.kt +++ b/vector/src/main/java/im/vector/app/features/usercode/UserCodeActivity.kt @@ -34,7 +34,9 @@ import im.vector.app.core.extensions.replaceFragment import im.vector.app.core.platform.VectorBaseActivity import im.vector.app.core.utils.onPermissionDeniedSnackbar import im.vector.app.databinding.ActivitySimpleBinding +import im.vector.app.features.analytics.plan.ViewRoom import im.vector.app.features.matrixto.MatrixToBottomSheet +import im.vector.app.features.matrixto.OriginOfMatrixTo import im.vector.app.features.qrcode.QrCodeScannerEvents import im.vector.app.features.qrcode.QrCodeScannerFragment import im.vector.app.features.qrcode.QrCodeScannerViewModel @@ -92,7 +94,7 @@ class UserCodeActivity : VectorBaseActivity(), } is UserCodeState.Mode.RESULT -> { showFragment(ShowUserCodeFragment::class) - MatrixToBottomSheet.withLink(mode.rawLink).show(supportFragmentManager, "MatrixToBottomSheet") + MatrixToBottomSheet.withLink(mode.rawLink, OriginOfMatrixTo.USER_CODE).show(supportFragmentManager, "MatrixToBottomSheet") } } } @@ -141,8 +143,8 @@ class UserCodeActivity : VectorBaseActivity(), } } - override fun mxToBottomSheetNavigateToRoom(roomId: String) { - navigator.openRoom(this, roomId) + override fun mxToBottomSheetNavigateToRoom(roomId: String, trigger: ViewRoom.Trigger?) { + navigator.openRoom(this, roomId, trigger = trigger) } override fun mxToBottomSheetSwitchToSpace(spaceId: String) {} diff --git a/vector/src/main/java/im/vector/app/features/usercode/UserCodeSharedViewModel.kt b/vector/src/main/java/im/vector/app/features/usercode/UserCodeSharedViewModel.kt index da894a42be..505bbdd2dd 100644 --- a/vector/src/main/java/im/vector/app/features/usercode/UserCodeSharedViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/usercode/UserCodeSharedViewModel.kt @@ -31,6 +31,7 @@ import kotlinx.coroutines.launch import org.matrix.android.sdk.api.extensions.tryOrNull import org.matrix.android.sdk.api.raw.RawService import org.matrix.android.sdk.api.session.Session +import org.matrix.android.sdk.api.session.getUser import org.matrix.android.sdk.api.session.permalinks.PermalinkData import org.matrix.android.sdk.api.session.permalinks.PermalinkParser import org.matrix.android.sdk.api.session.user.model.User @@ -74,11 +75,13 @@ class UserCodeSharedViewModel @AssistedInject constructor( private fun handleShareByText() { session.permalinkService().createPermalink(session.myUserId)?.let { permalink -> val text = stringProvider.getString(R.string.invite_friends_text, permalink) - _viewEvents.post(UserCodeShareViewEvents.SharePlainText( - text, - stringProvider.getString(R.string.invite_friends), - stringProvider.getString(R.string.invite_friends_rich_title) - )) + _viewEvents.post( + UserCodeShareViewEvents.SharePlainText( + text, + stringProvider.getString(R.string.invite_friends), + stringProvider.getString(R.string.invite_friends_rich_title) + ) + ) } } @@ -115,7 +118,7 @@ class UserCodeSharedViewModel @AssistedInject constructor( _viewEvents.post(UserCodeShareViewEvents.ToastMessage(stringProvider.getString(R.string.not_implemented))) } is PermalinkData.UserLink -> { - val user = tryOrNull { session.resolveUser(linkedId.userId) } + val user = tryOrNull { session.userService().resolveUser(linkedId.userId) } // Create raw Uxid in case the user is not searchable ?: User(linkedId.userId, null, null) diff --git a/vector/src/main/java/im/vector/app/features/userdirectory/UserListViewEvents.kt b/vector/src/main/java/im/vector/app/features/userdirectory/UserListViewEvents.kt index ef29e8015a..6e244d20bb 100644 --- a/vector/src/main/java/im/vector/app/features/userdirectory/UserListViewEvents.kt +++ b/vector/src/main/java/im/vector/app/features/userdirectory/UserListViewEvents.kt @@ -20,7 +20,7 @@ import im.vector.app.core.platform.VectorViewEvents import im.vector.app.features.discovery.ServerAndPolicies /** - * Transient events for invite users to room screen + * Transient events for invite users to room screen. */ sealed class UserListViewEvents : VectorViewEvents { data class Failure(val throwable: Throwable) : UserListViewEvents() diff --git a/vector/src/main/java/im/vector/app/features/userdirectory/UserListViewModel.kt b/vector/src/main/java/im/vector/app/features/userdirectory/UserListViewModel.kt index 783fddf8ad..b5f9d57970 100644 --- a/vector/src/main/java/im/vector/app/features/userdirectory/UserListViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/userdirectory/UserListViewModel.kt @@ -47,6 +47,7 @@ import org.matrix.android.sdk.api.session.identity.IdentityServiceListener import org.matrix.android.sdk.api.session.identity.ThreePid import org.matrix.android.sdk.api.session.user.model.User import org.matrix.android.sdk.api.util.toMatrixItem +import kotlin.random.Random data class ThreePidUser( val email: String, @@ -142,7 +143,7 @@ class UserListViewModel @AssistedInject constructor( } private fun retryUserSearch(state: UserListViewState) { - identityServerUsersSearch.tryEmit(UserSearch(state.searchTerm, cacheBuster = System.currentTimeMillis())) + identityServerUsersSearch.tryEmit(UserSearch(state.searchTerm, cacheBuster = Random.nextLong())) } private fun handleSearchUsers(searchTerm: String) { @@ -191,7 +192,7 @@ class UserListViewModel @AssistedInject constructor( knownUsersSearch .sample(300) .flatMapLatest { search -> - session.getPagedUsersLive(search, state.excludedUserIds).asFlow() + session.userService().getPagedUsersLive(search, state.excludedUserIds).asFlow() } .execute { copy(knownUsers = it) @@ -212,7 +213,7 @@ class UserListViewModel @AssistedInject constructor( ThreePidUser(email = search, user = null) } else { try { - val user = tryOrNull { session.getProfileAsUser(foundThreePid.matrixId) } ?: User(foundThreePid.matrixId) + val user = tryOrNull { session.profileService().getProfileAsUser(foundThreePid.matrixId) } ?: User(foundThreePid.matrixId) ThreePidUser( email = search, user = user @@ -232,10 +233,11 @@ class UserListViewModel @AssistedInject constructor( emptyList() } else { val searchResult = session + .userService() .searchUsersDirectory(search, 50, state.excludedUserIds.orEmpty()) .sortedBy { it.toMatrixItem().firstLetterOfDisplayName() } val userProfile = if (MatrixPatterns.isUserId(search)) { - val user = tryOrNull { session.getProfileAsUser(search) } + val user = tryOrNull { session.profileService().getProfileAsUser(search) } User( userId = search, displayName = user?.displayName, @@ -269,6 +271,6 @@ class UserListViewModel @AssistedInject constructor( private fun UserListViewState.hasNoIdentityServerConfigured() = matchingEmail is Fail && matchingEmail.error == IdentityServiceError.NoIdentityServerConfigured /** - * Wrapper class to allow identical search terms to be re-emitted + * Wrapper class to allow identical search terms to be re-emitted. */ private data class UserSearch(val searchTerm: String, val cacheBuster: Long = 0) diff --git a/vector/src/main/java/im/vector/app/features/voice/VoicePlayerHelper.kt b/vector/src/main/java/im/vector/app/features/voice/VoicePlayerHelper.kt index d2f7927d75..80db3fdc0c 100644 --- a/vector/src/main/java/im/vector/app/features/voice/VoicePlayerHelper.kt +++ b/vector/src/main/java/im/vector/app/features/voice/VoicePlayerHelper.kt @@ -20,11 +20,13 @@ import android.content.Context import android.os.Build import com.arthenica.ffmpegkit.FFmpegKit import com.arthenica.ffmpegkit.ReturnCode +import im.vector.app.core.time.Clock import timber.log.Timber import java.io.File import javax.inject.Inject class VoicePlayerHelper @Inject constructor( + private val clock: Clock, context: Context ) { private val outputDirectory: File by lazy { @@ -34,7 +36,7 @@ class VoicePlayerHelper @Inject constructor( } /** - * Ensure the file is encoded using aac audio codec + * Ensure the file is encoded using aac audio codec. */ fun convertFile(file: File): File? { return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { @@ -46,9 +48,9 @@ class VoicePlayerHelper @Inject constructor( if (targetFile.exists()) { targetFile.delete() } - val start = System.currentTimeMillis() + val start = clock.epochMillis() val session = FFmpegKit.execute("-i \"${file.path}\" -c:a aac \"${targetFile.path}\"") - val duration = System.currentTimeMillis() - start + val duration = clock.epochMillis() - start Timber.d("Convert to mp4 in $duration ms. Size in bytes from ${file.length()} to ${targetFile.length()}") return when { ReturnCode.isSuccess(session.returnCode) -> { diff --git a/vector/src/main/java/im/vector/app/features/voice/VoiceRecorder.kt b/vector/src/main/java/im/vector/app/features/voice/VoiceRecorder.kt index 691e064b8f..a5f4b52982 100644 --- a/vector/src/main/java/im/vector/app/features/voice/VoiceRecorder.kt +++ b/vector/src/main/java/im/vector/app/features/voice/VoiceRecorder.kt @@ -27,30 +27,30 @@ interface VoiceRecorder { fun initializeRecord(attachmentData: ContentAttachmentData) /** - * Start the recording + * Start the recording. * @param roomId id of the room to start record */ fun startRecord(roomId: String) /** - * Stop the recording + * Stop the recording. */ fun stopRecord() /** - * Remove the file + * Remove the file. */ fun cancelRecord() fun getMaxAmplitude(): Int /** - * Not guaranteed to be a ogg file + * Not guaranteed to be a ogg file. */ fun getCurrentRecord(): File? /** - * Guaranteed to be a ogg file + * Guaranteed to be a ogg file. */ fun getVoiceMessageFile(): File? } diff --git a/vector/src/main/java/im/vector/app/features/voice/VoiceRecorderL.kt b/vector/src/main/java/im/vector/app/features/voice/VoiceRecorderL.kt index 2d40f5f7a3..e5fd60dbc3 100644 --- a/vector/src/main/java/im/vector/app/features/voice/VoiceRecorderL.kt +++ b/vector/src/main/java/im/vector/app/features/voice/VoiceRecorderL.kt @@ -23,10 +23,14 @@ import com.arthenica.ffmpegkit.FFmpegKitConfig import com.arthenica.ffmpegkit.Level import com.arthenica.ffmpegkit.ReturnCode import im.vector.app.BuildConfig +import im.vector.app.core.time.Clock import timber.log.Timber import java.io.File -class VoiceRecorderL(context: Context) : AbstractVoiceRecorder(context, "mp4") { +class VoiceRecorderL( + context: Context, + private val clock: Clock, +) : AbstractVoiceRecorder(context, "mp4") { override fun setOutputFormat(mediaRecorder: MediaRecorder) { // Use AAC/MP4 format here mediaRecorder.setOutputFormat(MediaRecorder.OutputFormat.MPEG_4) @@ -43,9 +47,9 @@ class VoiceRecorderL(context: Context) : AbstractVoiceRecorder(context, "mp4") { if (targetFile.exists()) { targetFile.delete() } - val start = System.currentTimeMillis() + val start = clock.epochMillis() val session = FFmpegKit.execute("-i \"${recordedFile.path}\" -c:a libvorbis \"${targetFile.path}\"") - val duration = System.currentTimeMillis() - start + val duration = clock.epochMillis() - start Timber.d("Convert to ogg in $duration ms. Size in bytes from ${recordedFile.length()} to ${targetFile.length()}") return when { ReturnCode.isSuccess(session.returnCode) -> { diff --git a/vector/src/main/java/im/vector/app/features/voice/VoiceRecorderProvider.kt b/vector/src/main/java/im/vector/app/features/voice/VoiceRecorderProvider.kt index 004d520a6f..0cee8f4f6e 100644 --- a/vector/src/main/java/im/vector/app/features/voice/VoiceRecorderProvider.kt +++ b/vector/src/main/java/im/vector/app/features/voice/VoiceRecorderProvider.kt @@ -18,16 +18,18 @@ package im.vector.app.features.voice import android.content.Context import android.os.Build +import im.vector.app.core.time.Clock import javax.inject.Inject class VoiceRecorderProvider @Inject constructor( - private val context: Context + private val context: Context, + private val clock: Clock, ) { fun provideVoiceRecorder(): VoiceRecorder { return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { VoiceRecorderQ(context) } else { - VoiceRecorderL(context) + VoiceRecorderL(context, clock) } } } diff --git a/vector/src/main/java/im/vector/app/features/webview/ConsentWebViewEventListener.kt b/vector/src/main/java/im/vector/app/features/webview/ConsentWebViewEventListener.kt index a3d4902b41..e7f5ac29b9 100644 --- a/vector/src/main/java/im/vector/app/features/webview/ConsentWebViewEventListener.kt +++ b/vector/src/main/java/im/vector/app/features/webview/ConsentWebViewEventListener.kt @@ -32,7 +32,7 @@ private const val RIOT_BOT_ID = "@riot-bot:matrix.org" class ConsentWebViewEventListener(activity: VectorBaseActivity<*>, private val session: Session, private val delegate: WebViewEventListener) : - WebViewEventListener by delegate { + WebViewEventListener by delegate { private val safeActivity: VectorBaseActivity<*>? by weak(activity) @@ -44,7 +44,7 @@ class ConsentWebViewEventListener(activity: VectorBaseActivity<*>, } /** - * This methods try to create the RiotBot room when the user gives his agreement + * This methods try to create the RiotBot room when the user gives his agreement. */ private fun createRiotBotRoomIfNeeded() { safeActivity?.let { @@ -72,7 +72,7 @@ class ConsentWebViewEventListener(activity: VectorBaseActivity<*>, } /** - * APICallback instance + * APICallback instance. */ private val createRiotBotRoomCallback = object : MatrixCallback { override fun onSuccess(data: String) { diff --git a/vector/src/main/java/im/vector/app/features/webview/VectorWebViewClient.kt b/vector/src/main/java/im/vector/app/features/webview/VectorWebViewClient.kt index 597486491c..264c788315 100644 --- a/vector/src/main/java/im/vector/app/features/webview/VectorWebViewClient.kt +++ b/vector/src/main/java/im/vector/app/features/webview/VectorWebViewClient.kt @@ -59,9 +59,11 @@ class VectorWebViewClient(private val eventListener: WebViewEventListener) : Web override fun onReceivedHttpError(view: WebView, request: WebResourceRequest, errorResponse: WebResourceResponse) { super.onReceivedHttpError(view, request, errorResponse) - eventListener.onHttpError(request.url.toString(), + eventListener.onHttpError( + request.url.toString(), errorResponse.statusCode, - errorResponse.reasonPhrase) + errorResponse.reasonPhrase + ) } override fun onReceivedError(view: WebView, errorCode: Int, description: String, failingUrl: String) { diff --git a/vector/src/main/java/im/vector/app/features/webview/WebViewEventListener.kt b/vector/src/main/java/im/vector/app/features/webview/WebViewEventListener.kt index 897c913257..bd77283029 100644 --- a/vector/src/main/java/im/vector/app/features/webview/WebViewEventListener.kt +++ b/vector/src/main/java/im/vector/app/features/webview/WebViewEventListener.kt @@ -68,7 +68,7 @@ interface WebViewEventListener { } /** - * Triggered when a webview load an url + * Triggered when a webview load an url. * * @param url The url about to be rendered. * @return true if the method needs to manage some custom handling diff --git a/vector/src/main/java/im/vector/app/features/widgets/WidgetFragment.kt b/vector/src/main/java/im/vector/app/features/widgets/WidgetFragment.kt index dbd63186b6..cd2a4dcdf4 100644 --- a/vector/src/main/java/im/vector/app/features/widgets/WidgetFragment.kt +++ b/vector/src/main/java/im/vector/app/features/widgets/WidgetFragment.kt @@ -156,7 +156,8 @@ class WidgetFragment @Inject constructor() : integrationManagerActivityResultLauncher, state.roomId, state.widgetId, - state.widgetKind.screenId) + state.widgetKind.screenId + ) return@withState true } R.id.action_delete -> { diff --git a/vector/src/main/java/im/vector/app/features/widgets/WidgetPostAPIHandler.kt b/vector/src/main/java/im/vector/app/features/widgets/WidgetPostAPIHandler.kt index 51e7e6fcb7..3c88ea65a3 100644 --- a/vector/src/main/java/im/vector/app/features/widgets/WidgetPostAPIHandler.kt +++ b/vector/src/main/java/im/vector/app/features/widgets/WidgetPostAPIHandler.kt @@ -33,6 +33,8 @@ import org.matrix.android.sdk.api.session.events.model.Event import org.matrix.android.sdk.api.session.events.model.EventType import org.matrix.android.sdk.api.session.events.model.toContent import org.matrix.android.sdk.api.session.events.model.toModel +import org.matrix.android.sdk.api.session.getRoom +import org.matrix.android.sdk.api.session.room.getStateEvent import org.matrix.android.sdk.api.session.room.model.Membership import org.matrix.android.sdk.api.session.room.model.PowerLevelsContent import org.matrix.android.sdk.api.session.room.powerlevels.PowerLevelsHelper @@ -106,7 +108,7 @@ class WidgetPostAPIHandler @AssistedInject constructor(@Assisted private val roo } /** - * Retrieve the latest botOptions event + * Retrieve the latest botOptions event. * * @param eventData the modular data */ @@ -116,7 +118,7 @@ class WidgetPostAPIHandler @AssistedInject constructor(@Assisted private val roo } val userId = eventData["user_id"] as String Timber.d("Received request to get options for bot $userId in room $roomId requested") - val stateEvents = room.getStateEvents(setOf(EventType.BOT_OPTIONS)) + val stateEvents = room.stateService().getStateEvents(setOf(EventType.BOT_OPTIONS)) var botOptionsEvent: Event? = null val stateKey = "_$userId" for (stateEvent in stateEvents) { @@ -167,7 +169,7 @@ class WidgetPostAPIHandler @AssistedInject constructor(@Assisted private val roo } /** - * Provides the membership state + * Provides the membership state. * * @param eventData the modular data */ @@ -186,7 +188,7 @@ class WidgetPostAPIHandler @AssistedInject constructor(@Assisted private val roo } /** - * Request the latest joined room event + * Request the latest joined room event. * * @param eventData the modular data */ @@ -195,7 +197,7 @@ class WidgetPostAPIHandler @AssistedInject constructor(@Assisted private val roo return } Timber.d("Received request join rules in room $roomId") - val joinedEvents = room.getStateEvents(setOf(EventType.STATE_ROOM_JOIN_RULES)) + val joinedEvents = room.stateService().getStateEvents(setOf(EventType.STATE_ROOM_JOIN_RULES)) if (joinedEvents.isNotEmpty()) { widgetPostAPIMediator.sendObjectResponse(Event::class.java, joinedEvents.last(), eventData) } else { @@ -204,7 +206,7 @@ class WidgetPostAPIHandler @AssistedInject constructor(@Assisted private val roo } /** - * Provide the widgets list + * Provide the widgets list. * * @param eventData the modular data */ @@ -224,7 +226,7 @@ class WidgetPostAPIHandler @AssistedInject constructor(@Assisted private val roo } /** - * Set a new widget + * Set a new widget. * * @param eventData the modular data */ @@ -299,7 +301,7 @@ class WidgetPostAPIHandler @AssistedInject constructor(@Assisted private val roo } /** - * Update the 'plumbing state" + * Update the 'plumbing state". * * @param eventData the modular data */ @@ -315,7 +317,7 @@ class WidgetPostAPIHandler @AssistedInject constructor(@Assisted private val roo val params = HashMap() params["status"] = status launchWidgetAPIAction(widgetPostAPIMediator, eventData) { - room.sendStateEvent( + room.stateService().sendStateEvent( eventType = EventType.PLUMBING, stateKey = "", body = params @@ -324,7 +326,7 @@ class WidgetPostAPIHandler @AssistedInject constructor(@Assisted private val roo } /** - * Update the bot options + * Update the bot options. * * @param eventData the modular data */ @@ -340,7 +342,7 @@ class WidgetPostAPIHandler @AssistedInject constructor(@Assisted private val roo val stateKey = "_$userId" launchWidgetAPIAction(widgetPostAPIMediator, eventData) { - room.sendStateEvent( + room.stateService().sendStateEvent( eventType = EventType.BOT_OPTIONS, stateKey = stateKey, body = content @@ -349,7 +351,7 @@ class WidgetPostAPIHandler @AssistedInject constructor(@Assisted private val roo } /** - * Update the bot power levels + * Update the bot power levels. * * @param eventData the modular data */ @@ -371,7 +373,7 @@ class WidgetPostAPIHandler @AssistedInject constructor(@Assisted private val roo } /** - * Invite an user to this room + * Invite an user to this room. * * @param eventData the modular data */ @@ -382,18 +384,18 @@ class WidgetPostAPIHandler @AssistedInject constructor(@Assisted private val roo val userId = eventData["user_id"] as String val description = "Received request to invite $userId into room $roomId" Timber.d(description) - val member = room.getRoomMember(userId) + val member = room.membershipService().getRoomMember(userId) if (member != null && member.membership == Membership.JOIN) { widgetPostAPIMediator.sendSuccess(eventData) } else { launchWidgetAPIAction(widgetPostAPIMediator, eventData) { - room.invite(userId = userId) + room.membershipService().invite(userId = userId) } } } /** - * Provides the number of members in the rooms + * Provides the number of members in the rooms. * * @param eventData the modular data */ @@ -401,7 +403,7 @@ class WidgetPostAPIHandler @AssistedInject constructor(@Assisted private val roo if (checkRoomId(widgetPostAPIMediator, eventData)) { return } - val numberOfJoinedMembers = room.getNumberOfJoinedMembers() + val numberOfJoinedMembers = room.membershipService().getNumberOfJoinedMembers() widgetPostAPIMediator.sendIntegerResponse(numberOfJoinedMembers, eventData) } @@ -423,8 +425,8 @@ class WidgetPostAPIHandler @AssistedInject constructor(@Assisted private val roo } /** - * Check if roomId is present in the event and match - * Send response and return true in case of error + * Check if roomId is present in the event and match. + * Send response and return true in case of error. * * @return true in case of error */ @@ -446,8 +448,8 @@ class WidgetPostAPIHandler @AssistedInject constructor(@Assisted private val roo } /** - * Check if userId is present in the event - * Send response and return true in case of error + * Check if userId is present in the event. + * Send response and return true in case of error. * * @return true in case of error */ diff --git a/vector/src/main/java/im/vector/app/features/widgets/WidgetViewModel.kt b/vector/src/main/java/im/vector/app/features/widgets/WidgetViewModel.kt index 20fae6e31a..c7b1429304 100644 --- a/vector/src/main/java/im/vector/app/features/widgets/WidgetViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/widgets/WidgetViewModel.kt @@ -37,6 +37,7 @@ import org.matrix.android.sdk.api.session.Session import org.matrix.android.sdk.api.session.events.model.Content import org.matrix.android.sdk.api.session.events.model.EventType import org.matrix.android.sdk.api.session.events.model.toModel +import org.matrix.android.sdk.api.session.getRoom import org.matrix.android.sdk.api.session.integrationmanager.IntegrationManagerService import org.matrix.android.sdk.api.session.room.model.PowerLevelsContent import org.matrix.android.sdk.api.session.room.powerlevels.PowerLevelsHelper @@ -51,7 +52,7 @@ class WidgetViewModel @AssistedInject constructor(@Assisted val initialState: Wi widgetPostAPIHandlerFactory: WidgetPostAPIHandler.Factory, private val stringProvider: StringProvider, private val session: Session) : - VectorViewModel(initialState), + VectorViewModel(initialState), WidgetPostAPIHandler.NavigationCallback, IntegrationManagerService.Listener { diff --git a/vector/src/main/java/im/vector/app/features/widgets/permissions/RoomWidgetPermissionViewModel.kt b/vector/src/main/java/im/vector/app/features/widgets/permissions/RoomWidgetPermissionViewModel.kt index 78871da324..7ab2cf174d 100644 --- a/vector/src/main/java/im/vector/app/features/widgets/permissions/RoomWidgetPermissionViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/widgets/permissions/RoomWidgetPermissionViewModel.kt @@ -37,7 +37,7 @@ import java.net.URL class RoomWidgetPermissionViewModel @AssistedInject constructor(@Assisted val initialState: RoomWidgetPermissionViewState, private val session: Session) : - VectorViewModel(initialState) { + VectorViewModel(initialState) { private val widgetService = session.widgetService() private val integrationManagerService = session.integrationManagerService() diff --git a/vector/src/main/java/im/vector/app/features/workers/signout/ServerBackupStatusViewModel.kt b/vector/src/main/java/im/vector/app/features/workers/signout/ServerBackupStatusViewModel.kt index 8fb5b27376..8c24b2893a 100644 --- a/vector/src/main/java/im/vector/app/features/workers/signout/ServerBackupStatusViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/workers/signout/ServerBackupStatusViewModel.kt @@ -48,8 +48,8 @@ data class ServerBackupStatusViewState( ) : MavericksState /** - * The state representing the view - * It can take one state at a time + * The state representing the view. + * It can take one state at a time. */ sealed class BannerState { @@ -87,7 +87,7 @@ class ServerBackupStatusViewModel @AssistedInject constructor(@Assisted initialS val liveCrossSigningPrivateKeys = session.flow().liveCrossSigningPrivateKeys() combine(liveUserAccountData, liveCrossSigningInfo, keyBackupFlow, liveCrossSigningPrivateKeys) { _, crossSigningInfo, keyBackupState, pInfo -> // first check if 4S is already setup - if (session.sharedSecretStorageService.isRecoverySetup()) { + if (session.sharedSecretStorageService().isRecoverySetup()) { // 4S is already setup sp we should not display anything return@combine when (keyBackupState) { KeysBackupState.BackingUp -> BannerState.BackingUp @@ -121,21 +121,21 @@ class ServerBackupStatusViewModel @AssistedInject constructor(@Assisted initialS } /** - * Safe way to get the current KeysBackup version + * Safe way to get the current KeysBackup version. */ fun getCurrentBackupVersion(): String { return session.cryptoService().keysBackupService().currentBackupVersion ?: "" } /** - * Safe way to get the number of keys to backup + * Safe way to get the number of keys to backup. */ fun getNumberOfKeysToBackup(): Int { return session.cryptoService().inboundGroupSessionsCount(false) } /** - * Safe way to tell if there are more keys on the server + * Safe way to tell if there are more keys on the server. */ fun canRestoreKeys(): Boolean { return session.cryptoService().keysBackupService().canRestoreKeys() diff --git a/vector/src/main/java/im/vector/app/features/workers/signout/SignoutCheckViewModel.kt b/vector/src/main/java/im/vector/app/features/workers/signout/SignoutCheckViewModel.kt index fbc0b8fcff..79df62d7fb 100644 --- a/vector/src/main/java/im/vector/app/features/workers/signout/SignoutCheckViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/workers/signout/SignoutCheckViewModel.kt @@ -74,7 +74,7 @@ class SignoutCheckViewModel @AssistedInject constructor( session.cryptoService().keysBackupService().addListener(this) session.cryptoService().keysBackupService().checkAndStartKeysBackup() - val quad4SIsSetup = session.sharedSecretStorageService.isRecoverySetup() + val quad4SIsSetup = session.sharedSecretStorageService().isRecoverySetup() val allKeysKnown = session.cryptoService().crossSigningService().allPrivateKeysKnown() val backupState = session.cryptoService().keysBackupService().state setState { @@ -88,7 +88,7 @@ class SignoutCheckViewModel @AssistedInject constructor( session.flow().liveUserAccountData(setOf(MASTER_KEY_SSSS_NAME, USER_SIGNING_KEY_SSSS_NAME, SELF_SIGNING_KEY_SSSS_NAME)) .map { - session.sharedSecretStorageService.isRecoverySetup() + session.sharedSecretStorageService().isRecoverySetup() } .distinctUntilChanged() .execute { diff --git a/vector/src/main/res/drawable/ic_feedback.xml b/vector/src/main/res/drawable/ic_feedback.xml deleted file mode 100644 index e774a8ab5c..0000000000 --- a/vector/src/main/res/drawable/ic_feedback.xml +++ /dev/null @@ -1,14 +0,0 @@ - - - - diff --git a/vector/src/main/res/drawable/ic_presence_away.xml b/vector/src/main/res/drawable/ic_presence_away.xml new file mode 100644 index 0000000000..617fe735b6 --- /dev/null +++ b/vector/src/main/res/drawable/ic_presence_away.xml @@ -0,0 +1,24 @@ + + + + + + + + + + + + diff --git a/vector/src/main/res/drawable/ic_presence_busy.xml b/vector/src/main/res/drawable/ic_presence_busy.xml new file mode 100644 index 0000000000..9155a6b4a5 --- /dev/null +++ b/vector/src/main/res/drawable/ic_presence_busy.xml @@ -0,0 +1,24 @@ + + + + + + + + + + + + diff --git a/vector/src/main/res/drawable/ic_secure_backup.xml b/vector/src/main/res/drawable/ic_secure_backup.xml index 899bb8d2ae..78bb71e829 100644 --- a/vector/src/main/res/drawable/ic_secure_backup.xml +++ b/vector/src/main/res/drawable/ic_secure_backup.xml @@ -1,4 +1,5 @@ + android:fillColor="#2E2F32" + tools:fillColor="#FF0000"/> + android:fillColor="#2E2F32" + tools:fillColor="#FF0000"/> + android:fillType="evenOdd" + tools:fillColor="#FF0000"/> diff --git a/vector/src/main/res/drawable/ic_settings_root_flair.xml b/vector/src/main/res/drawable/ic_settings_root_flair.xml deleted file mode 100644 index b074ae478e..0000000000 --- a/vector/src/main/res/drawable/ic_settings_root_flair.xml +++ /dev/null @@ -1,9 +0,0 @@ - - - diff --git a/vector/src/main/res/drawable/ic_share_screen.xml b/vector/src/main/res/drawable/ic_share_screen.xml new file mode 100644 index 0000000000..5e3caa6987 --- /dev/null +++ b/vector/src/main/res/drawable/ic_share_screen.xml @@ -0,0 +1,10 @@ + + + diff --git a/vector/src/main/res/drawable/thread_filter_badge.xml b/vector/src/main/res/drawable/thread_filter_badge.xml new file mode 100644 index 0000000000..c9a01197c8 --- /dev/null +++ b/vector/src/main/res/drawable/thread_filter_badge.xml @@ -0,0 +1,29 @@ + + + + + + + + + + + + + + + + + + diff --git a/vector/src/main/res/layout/bottom_sheet_call_controls.xml b/vector/src/main/res/layout/bottom_sheet_call_controls.xml index e751ac412a..516dc29fa3 100644 --- a/vector/src/main/res/layout/bottom_sheet_call_controls.xml +++ b/vector/src/main/res/layout/bottom_sheet_call_controls.xml @@ -7,6 +7,15 @@ android:background="?colorSurface" android:orientation="vertical"> + + - - + + + - - + app:layout_constraintTop_toBottomOf="@id/timelineRecyclerView" /> + app:layout_constraintStart_toStartOf="parent" + tools:layout_height="300dp" /> + app:layout_constraintStart_toStartOf="parent" + tools:visibility="visible" /> + app:constraint_referenced_ids="composerLayout,notificationAreaView,failedMessagesWarningStub" /> - + +