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..50a9cdf5fc 100644 --- a/.github/ISSUE_TEMPLATE/release.yml +++ b/.github/ISSUE_TEMPLATE/release.yml @@ -106,7 +106,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/workflows/nightly.yml b/.github/workflows/nightly.yml index 84122095c6..502e3e275f 100644 --- a/.github/workflows/nightly.yml +++ b/.github/workflows/nightly.yml @@ -330,10 +330,9 @@ jobs: if: always() && github.event_name != 'workflow_dispatch' # 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 }} + hookshot_url: ${{ secrets.ELEMENT_ANDROID_HOOKSHOT_URL }} 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}}" 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 f952ec952a..8e42149545 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,3 +1,58 @@ +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) ====================================== 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/build.gradle b/build.gradle index 1ff1da7427..1d86f482da 100644 --- a/build.gradle +++ b/build.gradle @@ -21,8 +21,8 @@ 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" // NOTE: Do not place your application dependencies here; they belong // in the individual module build.gradle files } @@ -30,7 +30,7 @@ buildscript { // ktlint Plugin plugins { - id "org.jlleitschuh.gradle.ktlint" version "10.2.1" + id "org.jlleitschuh.gradle.ktlint" version "10.3.0" } // https://github.com/jeremylong/DependencyCheck diff --git a/dependencies_groups.gradle b/dependencies_groups.gradle index 2c3f6a0219..8422e05930 100644 --- a/dependencies_groups.gradle +++ b/dependencies_groups.gradle @@ -164,6 +164,7 @@ ext.groups = [ 'org.codehaus.woodstox', 'org.eclipse.ee4j', 'org.ec4j.core', + 'org.freemarker', 'org.glassfish.jaxb', 'org.hamcrest', 'org.jacoco', diff --git a/docs/pull_request.md b/docs/pull_request.md index b4dd0bd209..4775f292ee 100644 --- a/docs/pull_request.md +++ b/docs/pull_request.md @@ -32,14 +32,15 @@ Also, draft PR should not stay indefinitely in this state. It may be removed if ##### 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 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/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/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/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/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/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/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/lo/changelogs/40100100.txt b/fastlane/metadata/android/lo/changelogs/40100100.txt new file mode 100644 index 0000000000..fe0ee08327 --- /dev/null +++ b/fastlane/metadata/android/lo/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/changelogs/40100110.txt b/fastlane/metadata/android/lo/changelogs/40100110.txt new file mode 100644 index 0000000000..69139783ad --- /dev/null +++ b/fastlane/metadata/android/lo/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/changelogs/40100120.txt b/fastlane/metadata/android/lo/changelogs/40100120.txt new file mode 100644 index 0000000000..256cd1a437 --- /dev/null +++ b/fastlane/metadata/android/lo/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/changelogs/40100130.txt b/fastlane/metadata/android/lo/changelogs/40100130.txt new file mode 100644 index 0000000000..c4878ea5f6 --- /dev/null +++ b/fastlane/metadata/android/lo/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/changelogs/40100140.txt b/fastlane/metadata/android/lo/changelogs/40100140.txt new file mode 100644 index 0000000000..591b9b94b3 --- /dev/null +++ b/fastlane/metadata/android/lo/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/changelogs/40100150.txt b/fastlane/metadata/android/lo/changelogs/40100150.txt new file mode 100644 index 0000000000..98577742c7 --- /dev/null +++ b/fastlane/metadata/android/lo/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/changelogs/40100160.txt b/fastlane/metadata/android/lo/changelogs/40100160.txt new file mode 100644 index 0000000000..c369fc88d3 --- /dev/null +++ b/fastlane/metadata/android/lo/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/changelogs/40100170.txt b/fastlane/metadata/android/lo/changelogs/40100170.txt new file mode 100644 index 0000000000..776e1b6082 --- /dev/null +++ b/fastlane/metadata/android/lo/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/changelogs/40101000.txt b/fastlane/metadata/android/lo/changelogs/40101000.txt new file mode 100644 index 0000000000..f7d621404a --- /dev/null +++ b/fastlane/metadata/android/lo/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/changelogs/40101010.txt b/fastlane/metadata/android/lo/changelogs/40101010.txt new file mode 100644 index 0000000000..e2e1778b7a --- /dev/null +++ b/fastlane/metadata/android/lo/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/changelogs/40101020.txt b/fastlane/metadata/android/lo/changelogs/40101020.txt new file mode 100644 index 0000000000..581db95e90 --- /dev/null +++ b/fastlane/metadata/android/lo/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/changelogs/40101030.txt b/fastlane/metadata/android/lo/changelogs/40101030.txt new file mode 100644 index 0000000000..46d861995f --- /dev/null +++ b/fastlane/metadata/android/lo/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/changelogs/40101040.txt b/fastlane/metadata/android/lo/changelogs/40101040.txt new file mode 100644 index 0000000000..8ea7cac4f4 --- /dev/null +++ b/fastlane/metadata/android/lo/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/changelogs/40101050.txt b/fastlane/metadata/android/lo/changelogs/40101050.txt new file mode 100644 index 0000000000..2823c04b06 --- /dev/null +++ b/fastlane/metadata/android/lo/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/changelogs/40101060.txt b/fastlane/metadata/android/lo/changelogs/40101060.txt new file mode 100644 index 0000000000..0cdea9e2c7 --- /dev/null +++ b/fastlane/metadata/android/lo/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/changelogs/40101070.txt b/fastlane/metadata/android/lo/changelogs/40101070.txt new file mode 100644 index 0000000000..08926229f9 --- /dev/null +++ b/fastlane/metadata/android/lo/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/changelogs/40101080.txt b/fastlane/metadata/android/lo/changelogs/40101080.txt new file mode 100644 index 0000000000..f2947aae63 --- /dev/null +++ b/fastlane/metadata/android/lo/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/changelogs/40101090.txt b/fastlane/metadata/android/lo/changelogs/40101090.txt new file mode 100644 index 0000000000..129f86c562 --- /dev/null +++ b/fastlane/metadata/android/lo/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/changelogs/40101100.txt b/fastlane/metadata/android/lo/changelogs/40101100.txt new file mode 100644 index 0000000000..337137d3bc --- /dev/null +++ b/fastlane/metadata/android/lo/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/changelogs/40101110.txt b/fastlane/metadata/android/lo/changelogs/40101110.txt new file mode 100644 index 0000000000..f95972eaa9 --- /dev/null +++ b/fastlane/metadata/android/lo/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/changelogs/40101120.txt b/fastlane/metadata/android/lo/changelogs/40101120.txt new file mode 100644 index 0000000000..a613e176aa --- /dev/null +++ b/fastlane/metadata/android/lo/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/changelogs/40101130.txt b/fastlane/metadata/android/lo/changelogs/40101130.txt new file mode 100644 index 0000000000..3cc85bc6f8 --- /dev/null +++ b/fastlane/metadata/android/lo/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/changelogs/40101140.txt b/fastlane/metadata/android/lo/changelogs/40101140.txt new file mode 100644 index 0000000000..11a5df1059 --- /dev/null +++ b/fastlane/metadata/android/lo/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/changelogs/40101150.txt b/fastlane/metadata/android/lo/changelogs/40101150.txt new file mode 100644 index 0000000000..8751496a49 --- /dev/null +++ b/fastlane/metadata/android/lo/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/changelogs/40101160.txt b/fastlane/metadata/android/lo/changelogs/40101160.txt new file mode 100644 index 0000000000..c102140102 --- /dev/null +++ b/fastlane/metadata/android/lo/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/changelogs/40102000.txt b/fastlane/metadata/android/lo/changelogs/40102000.txt new file mode 100644 index 0000000000..69f1667b93 --- /dev/null +++ b/fastlane/metadata/android/lo/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/changelogs/40102010.txt b/fastlane/metadata/android/lo/changelogs/40102010.txt new file mode 100644 index 0000000000..237b48ba3b --- /dev/null +++ b/fastlane/metadata/android/lo/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/changelogs/40103000.txt b/fastlane/metadata/android/lo/changelogs/40103000.txt new file mode 100644 index 0000000000..8e386df4a7 --- /dev/null +++ b/fastlane/metadata/android/lo/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/changelogs/40103010.txt b/fastlane/metadata/android/lo/changelogs/40103010.txt new file mode 100644 index 0000000000..0a6a6461fd --- /dev/null +++ b/fastlane/metadata/android/lo/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/changelogs/40103020.txt b/fastlane/metadata/android/lo/changelogs/40103020.txt new file mode 100644 index 0000000000..8b9ed12a3a --- /dev/null +++ b/fastlane/metadata/android/lo/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/changelogs/40103030.txt b/fastlane/metadata/android/lo/changelogs/40103030.txt new file mode 100644 index 0000000000..6374f4fdf5 --- /dev/null +++ b/fastlane/metadata/android/lo/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/changelogs/40103040.txt b/fastlane/metadata/android/lo/changelogs/40103040.txt new file mode 100644 index 0000000000..896d9190ce --- /dev/null +++ b/fastlane/metadata/android/lo/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/changelogs/40103050.txt b/fastlane/metadata/android/lo/changelogs/40103050.txt new file mode 100644 index 0000000000..b76db1a80c --- /dev/null +++ b/fastlane/metadata/android/lo/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/changelogs/40103060.txt b/fastlane/metadata/android/lo/changelogs/40103060.txt new file mode 100644 index 0000000000..2e97183a56 --- /dev/null +++ b/fastlane/metadata/android/lo/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/changelogs/40103070.txt b/fastlane/metadata/android/lo/changelogs/40103070.txt new file mode 100644 index 0000000000..47eb6c7b4d --- /dev/null +++ b/fastlane/metadata/android/lo/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/changelogs/40103080.txt b/fastlane/metadata/android/lo/changelogs/40103080.txt new file mode 100644 index 0000000000..a12d374d20 --- /dev/null +++ b/fastlane/metadata/android/lo/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/changelogs/40103090.txt b/fastlane/metadata/android/lo/changelogs/40103090.txt new file mode 100644 index 0000000000..ee93ca0a96 --- /dev/null +++ b/fastlane/metadata/android/lo/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/changelogs/40103100.txt b/fastlane/metadata/android/lo/changelogs/40103100.txt new file mode 100644 index 0000000000..b8b65387ef --- /dev/null +++ b/fastlane/metadata/android/lo/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/changelogs/40103110.txt b/fastlane/metadata/android/lo/changelogs/40103110.txt new file mode 100644 index 0000000000..f64ee3b469 --- /dev/null +++ b/fastlane/metadata/android/lo/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/changelogs/40103120.txt b/fastlane/metadata/android/lo/changelogs/40103120.txt new file mode 100644 index 0000000000..a7b8412cfb --- /dev/null +++ b/fastlane/metadata/android/lo/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/changelogs/40103130.txt b/fastlane/metadata/android/lo/changelogs/40103130.txt new file mode 100644 index 0000000000..cb725ee68c --- /dev/null +++ b/fastlane/metadata/android/lo/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/changelogs/40103140.txt b/fastlane/metadata/android/lo/changelogs/40103140.txt new file mode 100644 index 0000000000..1f72757cf1 --- /dev/null +++ b/fastlane/metadata/android/lo/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/changelogs/40103150.txt b/fastlane/metadata/android/lo/changelogs/40103150.txt new file mode 100644 index 0000000000..c11acd15d6 --- /dev/null +++ b/fastlane/metadata/android/lo/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/changelogs/40103160.txt b/fastlane/metadata/android/lo/changelogs/40103160.txt new file mode 100644 index 0000000000..a17f218463 --- /dev/null +++ b/fastlane/metadata/android/lo/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/changelogs/40103170.txt b/fastlane/metadata/android/lo/changelogs/40103170.txt new file mode 100644 index 0000000000..b8337a19cc --- /dev/null +++ b/fastlane/metadata/android/lo/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/changelogs/40103180.txt b/fastlane/metadata/android/lo/changelogs/40103180.txt new file mode 100644 index 0000000000..e6b65ff812 --- /dev/null +++ b/fastlane/metadata/android/lo/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/changelogs/40104000.txt b/fastlane/metadata/android/lo/changelogs/40104000.txt new file mode 100644 index 0000000000..9d849da5b7 --- /dev/null +++ b/fastlane/metadata/android/lo/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/changelogs/40104020.txt b/fastlane/metadata/android/lo/changelogs/40104020.txt new file mode 100644 index 0000000000..358ca7fd39 --- /dev/null +++ b/fastlane/metadata/android/lo/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/changelogs/40104040.txt b/fastlane/metadata/android/lo/changelogs/40104040.txt new file mode 100644 index 0000000000..7bfaf76bf2 --- /dev/null +++ b/fastlane/metadata/android/lo/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/changelogs/40104060.txt b/fastlane/metadata/android/lo/changelogs/40104060.txt new file mode 100644 index 0000000000..10ae599698 --- /dev/null +++ b/fastlane/metadata/android/lo/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/changelogs/40104070.txt b/fastlane/metadata/android/lo/changelogs/40104070.txt new file mode 100644 index 0000000000..4ede801350 --- /dev/null +++ b/fastlane/metadata/android/lo/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/changelogs/40104080.txt b/fastlane/metadata/android/lo/changelogs/40104080.txt new file mode 100644 index 0000000000..5d7de22065 --- /dev/null +++ b/fastlane/metadata/android/lo/changelogs/40104080.txt @@ -0,0 +1,2 @@ +ການປ່ຽນແປງຫຼັກໃນສະບັບນີ້: ເລື່ອນໃນຂໍ້ຄວາມສຽງ. ແກ້ໄຂບັກຫຼາຍບ່ອນ ແລະ ປັບປຸງຄວາມສະຖຽນ. +ບັນທຶກການປ່ຽນແປງສະບັບເຕັມ: https://github.com/vector-im/element-android/releases diff --git a/fastlane/metadata/android/lo/changelogs/40104100.txt b/fastlane/metadata/android/lo/changelogs/40104100.txt new file mode 100644 index 0000000000..5d7de22065 --- /dev/null +++ b/fastlane/metadata/android/lo/changelogs/40104100.txt @@ -0,0 +1,2 @@ +ການປ່ຽນແປງຫຼັກໃນສະບັບນີ້: ເລື່ອນໃນຂໍ້ຄວາມສຽງ. ແກ້ໄຂບັກຫຼາຍບ່ອນ ແລະ ປັບປຸງຄວາມສະຖຽນ. +ບັນທຶກການປ່ຽນແປງສະບັບເຕັມ: https://github.com/vector-im/element-android/releases diff --git a/fastlane/metadata/android/lo/changelogs/40104110.txt b/fastlane/metadata/android/lo/changelogs/40104110.txt new file mode 100644 index 0000000000..edc025cb39 --- /dev/null +++ b/fastlane/metadata/android/lo/changelogs/40104110.txt @@ -0,0 +1,2 @@ +ການປ່ຽນແປງຫຼັກໃນສະບັບນີ້: ການແກ້ໄຂ bug ຕ່າງໆແລະການປັບປຸງຄວາມສະຖຽນ. +ບັນທຶກການປ່ຽນແປງສະບັບເຕັມ: https://github.com/vector-im/element-android/releases diff --git a/fastlane/metadata/android/lo/full_description.txt b/fastlane/metadata/android/lo/full_description.txt new file mode 100644 index 0000000000..094c1b57f5 --- /dev/null +++ b/fastlane/metadata/android/lo/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/short_description.txt b/fastlane/metadata/android/lo/short_description.txt new file mode 100644 index 0000000000..625b078915 --- /dev/null +++ b/fastlane/metadata/android/lo/short_description.txt @@ -0,0 +1 @@ +ສົ່ງຂໍ້ຄວາມກຸ່ມ - ສົ່ງຂໍ້ຄວາມທີ່ເຂົ້າລະຫັດ, ສົນທະນາກຸ່ມ ແລະໂທວິດີໂອ diff --git a/fastlane/metadata/android/lo/title.txt b/fastlane/metadata/android/lo/title.txt new file mode 100644 index 0000000000..a9054d803b --- /dev/null +++ b/fastlane/metadata/android/lo/title.txt @@ -0,0 +1 @@ +ລະບົບສື່ສານທີ່ມີຄວາມປອດໄພສູງ 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/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/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/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/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/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/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/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/CameraPicker.kt b/library/multipicker/src/main/java/im/vector/lib/multipicker/CameraPicker.kt index b1442a56e1..785b9fae43 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,11 +23,9 @@ 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 @@ -38,7 +36,7 @@ class CameraPicker { * 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..59601b30d9 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,11 +23,9 @@ 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 @@ -38,7 +36,7 @@ class CameraVideoPicker { * 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/MultiPicker.kt b/library/multipicker/src/main/java/im/vector/lib/multipicker/MultiPicker.kt index 6ce50f622a..821c2f0d4c 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 @@ -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/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/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 816b9716bd..2aab8510ce 100644 --- a/matrix-sdk-android/build.gradle +++ b/matrix-sdk-android/build.gradle @@ -14,6 +14,27 @@ buildscript { } } +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 +53,7 @@ android { // that the app's state is completely cleared between tests. testInstrumentationRunnerArguments clearPackageData: 'true' - buildConfigField "String", "SDK_VERSION", "\"1.4.13\"" + buildConfigField "String", "SDK_VERSION", "\"1.4.14\"" buildConfigField "String", "GIT_SDK_REVISION", "\"${gitRevision()}\"" buildConfigField "String", "GIT_SDK_REVISION_UNIX_DATE", "\"${gitRevisionUnixDate()}\"" @@ -171,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.47' testImplementation libs.tests.junit testImplementation 'org.robolectric:robolectric:4.7.3' diff --git a/matrix-sdk-android/docs/modules.md b/matrix-sdk-android/docs/modules.md new file mode 100644 index 0000000000..edf5af64d0 --- /dev/null +++ b/matrix-sdk-android/docs/modules.md @@ -0,0 +1,18 @@ +# Module matrix-sdk-android + +## 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**: The app will have to create and manage a Matrix object. +- From this **Matrix** object, you will be able to get various services, including the **AuthenticationService**. +- With this **AuthenticationService** you will be able to get an existing **Session**, or create one using a **LoginWizard** or a **RegistrationWizard**, which will finally give you a **Session**. +- From the **Session**, you will be able to retrieve many Services, including the **RoomService**. +- From the **RoomService**, you will be able to list the rooms, create a **Room**, and get a specific **Room**. +- And from a **Room**, you will be able to do many things, including get a **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/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..52dbfc7155 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) { 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..4b9e605cd0 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,7 +57,8 @@ 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 @@ -145,7 +146,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 +166,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 +216,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 +239,7 @@ class CommonTestHelper(context: Context) { password: String, testParams: SessionTestParams): Session { val session = createAccountAndSync( - userNamePrefix + "_" + System.currentTimeMillis() + UUID.randomUUID(), + userNamePrefix + "_" + accountNumber++ + "_" + UUID.randomUUID(), password, testParams ) @@ -431,7 +433,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..058b1f7933 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 @@ -40,6 +40,7 @@ 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.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 @@ -64,12 +65,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 +99,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 +109,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 +126,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 +162,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 @@ -242,8 +244,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 +258,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 +271,7 @@ class CryptoTestHelper(private val testHelper: CommonTestHelper) { } } bobRoomSummariesLive.observeForever(newRoomObserver) - bob.joinRoom(roomId) + bob.roomService().joinRoom(roomId) } return roomId @@ -367,20 +370,20 @@ 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) } 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..3f75aa0979 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 @@ -27,6 +27,7 @@ import org.junit.runner.RunWith import org.matrix.android.sdk.InstrumentedTest 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 @@ -37,6 +38,7 @@ private const val DUMMY_DEVICE_KEY = "DeviceKey" class CryptoStoreTest : InstrumentedTest { private val cryptoStoreHelper = CryptoStoreHelper() + private val clock = DefaultClock() @Before fun setup() { @@ -106,7 +108,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 +116,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..88d99f12e0 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 @@ -38,8 +38,11 @@ import org.matrix.android.sdk.api.session.crypto.model.OlmDecryptionResult 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.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 @@ -86,7 +89,7 @@ class E2eeSanityTests : InstrumentedTest { otherAccounts.forEach { testHelper.runBlockingTest { Log.v("#E2E TEST", "Alice invites ${it.myUserId}") - aliceRoomPOV.invite(it.myUserId) + aliceRoomPOV.membershipService().invite(it.myUserId) } } @@ -128,7 +131,7 @@ class E2eeSanityTests : InstrumentedTest { newAccount.forEach { testHelper.runBlockingTest { Log.v("#E2E TEST", "Alice invites ${it.myUserId}") - aliceRoomPOV.invite(it.myUserId) + aliceRoomPOV.membershipService().invite(it.myUserId) } } @@ -523,10 +526,10 @@ class E2eeSanityTests : InstrumentedTest { } private fun sendMessageInRoom(aliceRoomPOV: Room, text: String): String? { - aliceRoomPOV.sendTextMessage(text) + 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) { @@ -551,7 +554,7 @@ class E2eeSanityTests : InstrumentedTest { 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 } @@ -574,7 +577,7 @@ class E2eeSanityTests : InstrumentedTest { testHelper.runBlockingTest(60_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 } 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..8a1edec5e3 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 @@ -29,6 +29,8 @@ 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 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..de4a928dc3 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) @@ -185,7 +186,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) 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..0f3ff7898f 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 @@ -110,11 +110,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) } @@ -149,16 +151,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..592d24fb69 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 @@ -50,6 +50,8 @@ import org.matrix.android.sdk.api.session.crypto.verification.VerificationTransa 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.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 @@ -73,7 +75,7 @@ class KeyShareTests : InstrumentedTest { // Create an encrypted room and add a message val roomId = commonTestHelper.runBlockingTest { - aliceSession.createRoom( + aliceSession.roomService().createRoom( CreateRoomParams().apply { visibility = RoomDirectoryVisibility.PRIVATE enableEncryption() @@ -83,7 +85,7 @@ class KeyShareTests : InstrumentedTest { val room = aliceSession.getRoom(roomId) assertNotNull(room) Thread.sleep(4_000) - assertTrue(room?.isEncrypted() == true) + assertTrue(room?.roomCryptoService()?.isEncrypted() == true) val sentEventId = commonTestHelper.sendTextMessage(room!!, "My Message", 1).first().eventId // Open a new sessionx @@ -340,7 +342,7 @@ class KeyShareTests : InstrumentedTest { // Create an encrypted room and send a couple of messages val roomId = commonTestHelper.runBlockingTest { - aliceSession.createRoom( + aliceSession.roomService().createRoom( CreateRoomParams().apply { visibility = RoomDirectoryVisibility.PRIVATE enableEncryption() @@ -350,7 +352,7 @@ class KeyShareTests : InstrumentedTest { val roomAlicePov = aliceSession.getRoom(roomId) assertNotNull(roomAlicePov) Thread.sleep(1_000) - assertTrue(roomAlicePov?.isEncrypted() == true) + assertTrue(roomAlicePov?.roomCryptoService()?.isEncrypted() == true) val secondEventId = commonTestHelper.sendTextMessage(roomAlicePov!!, "Message", 3)[1].eventId // Create bob session @@ -374,11 +376,11 @@ class KeyShareTests : InstrumentedTest { // Let alice invite bob commonTestHelper.runBlockingTest { - roomAlicePov.invite(bobSession.myUserId, null) + roomAlicePov.membershipService().invite(bobSession.myUserId, null) } commonTestHelper.runBlockingTest { - bobSession.joinRoom(roomAlicePov.roomId, null, emptyList()) + bobSession.roomService().joinRoom(roomAlicePov.roomId, null, emptyList()) } // we want to discard alice outbound session 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..bad9fd0f68 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 @@ -33,6 +33,8 @@ 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 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..3220f161fa 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 @@ -41,6 +41,7 @@ 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.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 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..a882f69013 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,14 @@ 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 +236,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,7 +245,7 @@ class QuadSTests : InstrumentedTest { testHelper.runBlockingTest { try { - aliceSession.sharedSecretStorageService.getSecret("my.secret", + aliceSession.sharedSecretStorageService().getSecret("my.secret", keyId1, RawBytesKeySpec.fromPassphrase( "A bad passphrase", @@ -260,7 +260,7 @@ class QuadSTests : InstrumentedTest { // Now try with correct key testHelper.runBlockingTest { - aliceSession.sharedSecretStorageService.getSecret("my.secret", + aliceSession.sharedSecretStorageService().getSecret("my.secret", keyId1, RawBytesKeySpec.fromPassphrase( passphrase, @@ -292,7 +292,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 +312,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 { 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..374d709505 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 @@ -155,8 +155,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 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..f6e08a576e 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 @@ -80,7 +81,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) @@ -141,7 +142,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) @@ -214,7 +215,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) @@ -309,7 +310,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..27d3fdc856 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,7 +61,7 @@ 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( @@ -75,7 +77,7 @@ 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( @@ -153,7 +155,7 @@ 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( 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..3864ea1cd1 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 @@ -74,7 +75,7 @@ class TimelineForwardPaginationTest : InstrumentedTest { // 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..5d09b74e6c 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 { 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..251b2c614c 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 @@ -65,7 +66,7 @@ class TimelineSimpleBackPaginationTest : InstrumentedTest { message, 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..ab0bbe7f73 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, 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 50e4a6feb6..d2c8b52fc7 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() } @@ -203,27 +206,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 +236,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) @@ -279,7 +282,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" }) @@ -319,7 +322,7 @@ class SpaceHierarchyTest : InstrumentedTest { 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 +349,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 +362,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 +388,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 +417,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, @@ -496,22 +500,22 @@ class SpaceHierarchyTest : InstrumentedTest { )) 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 -> @@ -551,7 +555,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/matrix/android/sdk/api/Matrix.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/Matrix.kt index e7b4b766ad..217f7e3da8 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 @@ -43,7 +43,8 @@ import javax.inject.Inject /** * This is the main entry point to the matrix sdk. - * To get the singleton instance, use getInstance static method. + *
+ * See [Companion.createInstance] to create an instance. The app should create and manage the instance itself. */ class Matrix private constructor(context: Context, matrixConfiguration: MatrixConfiguration) { @@ -74,9 +75,7 @@ class Matrix private constructor(context: Context, matrixConfiguration: MatrixCo fun getUserAgent() = userAgentHolder.userAgent - fun authenticationService(): AuthenticationService { - return authenticationService - } + fun authenticationService() = authenticationService fun rawService() = rawService @@ -84,9 +83,7 @@ class Matrix private constructor(context: Context, matrixConfiguration: MatrixCo fun homeServerHistoryService() = homeServerHistoryService - fun legacySessionImporter(): LegacySessionImporter { - return legacySessionImporter - } + fun legacySessionImporter() = legacySessionImporter fun workerFactory(): WorkerFactory = matrixWorkerFactory @@ -143,6 +140,9 @@ class Matrix private constructor(context: Context, matrixConfiguration: MatrixCo return instance } + /** + * @return a String with details about the Matrix SDK version + */ fun getSdkVersion(): String { return BuildConfig.SDK_VERSION + " (" + BuildConfig.GIT_SDK_REVISION + ")" } 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/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 95% 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..1a4c1ee51c 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,127 +1,127 @@ -/* - * Copyright 2019 New Vector Ltd - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package im.vector.app.features.login.terms - -import org.matrix.android.sdk.api.auth.data.LocalizedFlowDataLoginTerms -import org.matrix.android.sdk.api.auth.registration.TermPolicies - -/** - * This method extract the policies from the login terms parameter, regarding the user language. - * For each policy, if user language is not found, the default language is used and if not found, the first url and name are used (not predictable) - * - * Example of Data: - *
- * "m.login.terms": {
- *       "policies": {
- *         "privacy_policy": {
- *           "version": "1.0",
- *           "en": {
- *             "url": "http:\/\/matrix.org\/_matrix\/consent?v=1.0",
- *             "name": "Terms and Conditions"
- *           }
- *         }
- *       }
- *     }
- *
- * - * @param userLanguage the user language - * @param defaultLanguage the default language to use if the user language is not found for a policy in registrationFlowResponse - */ -fun TermPolicies.toLocalizedLoginTerms(userLanguage: String, - defaultLanguage: String = "en"): List { - val result = ArrayList() - - val policies = get("policies") - if (policies is Map<*, *>) { - policies.keys.forEach { policyName -> - val localizedFlowDataLoginTermsPolicyName = policyName as String - var localizedFlowDataLoginTermsVersion: String? = null - var localizedFlowDataLoginTermsLocalizedUrl: String? = null - var localizedFlowDataLoginTermsLocalizedName: String? = null - - val policy = policies[policyName] - - // Enter this policy - if (policy is Map<*, *>) { - // Version - localizedFlowDataLoginTermsVersion = policy["version"] as String? - - var userLanguageUrlAndName: UrlAndName? = null - var defaultLanguageUrlAndName: UrlAndName? = null - var firstUrlAndName: UrlAndName? = null - - // Search for language - policy.keys.forEach { policyKey -> - when (policyKey) { - "version" -> Unit // Ignore - userLanguage -> { - // We found the data for the user language - userLanguageUrlAndName = extractUrlAndName(policy[policyKey]) - } - defaultLanguage -> { - // We found default language - defaultLanguageUrlAndName = extractUrlAndName(policy[policyKey]) - } - else -> { - if (firstUrlAndName == null) { - // Get at least some data - firstUrlAndName = extractUrlAndName(policy[policyKey]) - } - } - } - } - - // Copy found language data by priority - when { - userLanguageUrlAndName != null -> { - localizedFlowDataLoginTermsLocalizedUrl = userLanguageUrlAndName!!.url - localizedFlowDataLoginTermsLocalizedName = userLanguageUrlAndName!!.name - } - defaultLanguageUrlAndName != null -> { - localizedFlowDataLoginTermsLocalizedUrl = defaultLanguageUrlAndName!!.url - localizedFlowDataLoginTermsLocalizedName = defaultLanguageUrlAndName!!.name - } - firstUrlAndName != null -> { - localizedFlowDataLoginTermsLocalizedUrl = firstUrlAndName!!.url - localizedFlowDataLoginTermsLocalizedName = firstUrlAndName!!.name - } - } - } - - result.add(LocalizedFlowDataLoginTerms( - policyName = localizedFlowDataLoginTermsPolicyName, - version = localizedFlowDataLoginTermsVersion, - localizedUrl = localizedFlowDataLoginTermsLocalizedUrl, - localizedName = localizedFlowDataLoginTermsLocalizedName - )) - } - } - - return result -} - -private fun extractUrlAndName(policyData: Any?): UrlAndName? { - if (policyData is Map<*, *>) { - val url = policyData["url"] as String? - val name = policyData["name"] as String? - - if (url != null && name != null) { - return UrlAndName(url, name) - } - } - return null -} +/* + * 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.auth + +import org.matrix.android.sdk.api.auth.data.LocalizedFlowDataLoginTerms +import org.matrix.android.sdk.api.auth.registration.TermPolicies + +/** + * This method extract the policies from the login terms parameter, regarding the user language. + * For each policy, if user language is not found, the default language is used and if not found, the first url and name are used (not predictable) + * + * Example of Data: + *
+ * "m.login.terms": {
+ *       "policies": {
+ *         "privacy_policy": {
+ *           "version": "1.0",
+ *           "en": {
+ *             "url": "http:\/\/matrix.org\/_matrix\/consent?v=1.0",
+ *             "name": "Terms and Conditions"
+ *           }
+ *         }
+ *       }
+ *     }
+ *
+ * + * @param userLanguage the user language + * @param defaultLanguage the default language to use if the user language is not found for a policy in registrationFlowResponse + */ +fun TermPolicies.toLocalizedLoginTerms(userLanguage: String, + defaultLanguage: String = "en"): List { + val result = ArrayList() + + val policies = get("policies") + if (policies is Map<*, *>) { + policies.keys.forEach { policyName -> + val localizedFlowDataLoginTermsPolicyName = policyName as String + var localizedFlowDataLoginTermsVersion: String? = null + var localizedFlowDataLoginTermsLocalizedUrl: String? = null + var localizedFlowDataLoginTermsLocalizedName: String? = null + + val policy = policies[policyName] + + // Enter this policy + if (policy is Map<*, *>) { + // Version + localizedFlowDataLoginTermsVersion = policy["version"] as String? + + var userLanguageUrlAndName: UrlAndName? = null + var defaultLanguageUrlAndName: UrlAndName? = null + var firstUrlAndName: UrlAndName? = null + + // Search for language + policy.keys.forEach { policyKey -> + when (policyKey) { + "version" -> Unit // Ignore + userLanguage -> { + // We found the data for the user language + userLanguageUrlAndName = extractUrlAndName(policy[policyKey]) + } + defaultLanguage -> { + // We found default language + defaultLanguageUrlAndName = extractUrlAndName(policy[policyKey]) + } + else -> { + if (firstUrlAndName == null) { + // Get at least some data + firstUrlAndName = extractUrlAndName(policy[policyKey]) + } + } + } + } + + // Copy found language data by priority + when { + userLanguageUrlAndName != null -> { + localizedFlowDataLoginTermsLocalizedUrl = userLanguageUrlAndName!!.url + localizedFlowDataLoginTermsLocalizedName = userLanguageUrlAndName!!.name + } + defaultLanguageUrlAndName != null -> { + localizedFlowDataLoginTermsLocalizedUrl = defaultLanguageUrlAndName!!.url + localizedFlowDataLoginTermsLocalizedName = defaultLanguageUrlAndName!!.name + } + firstUrlAndName != null -> { + localizedFlowDataLoginTermsLocalizedUrl = firstUrlAndName!!.url + localizedFlowDataLoginTermsLocalizedName = firstUrlAndName!!.name + } + } + } + + result.add(LocalizedFlowDataLoginTerms( + policyName = localizedFlowDataLoginTermsPolicyName, + version = localizedFlowDataLoginTermsVersion, + localizedUrl = localizedFlowDataLoginTermsLocalizedUrl, + localizedName = localizedFlowDataLoginTermsLocalizedName + )) + } + } + + return result +} + +private fun extractUrlAndName(policyData: Any?): UrlAndName? { + if (policyData is Map<*, *>) { + val url = policyData["url"] as String? + val name = policyData["name"] as String? + + if (url != null && name != null) { + return UrlAndName(url, name) + } + } + return null +} 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/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/api/failure/InitialSyncRequestReason.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/failure/InitialSyncRequestReason.kt new file mode 100644 index 0000000000..ebe07823f4 --- /dev/null +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/failure/InitialSyncRequestReason.kt @@ -0,0 +1,27 @@ +/* + * 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.failure + +/** + * This enum provide the reason why the SDK request an initial sync to the application + */ +enum class InitialSyncRequestReason { + /** + * The list of ignored users has changed, and at least one user who was ignored is not ignored anymore + */ + IGNORED_USERS_LIST_CHANGE, +} 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..19502f0b46 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,26 +67,7 @@ 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 @@ -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]. @@ -206,6 +191,96 @@ interface Session : */ fun identityService(): IdentityService + /** + * 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 */ @@ -266,6 +341,11 @@ interface 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. @@ -298,12 +378,11 @@ interface Session : * Possible cases: * - The access token is not valid anymore, * - 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 /** 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..aeb0e7e4ee --- /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/crypto/model/IncomingRequestCancellation.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/model/IncomingRequestCancellation.kt index 74ca7304f7..ad11ef9a5e 100755 --- 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 @@ -46,8 +46,9 @@ data class IncomingRequestCancellation( * Factory * * @param event the event + * @param currentTimeMillis the current time in milliseconds */ - fun fromEvent(event: Event): IncomingRequestCancellation? { + fun fromEvent(event: Event, currentTimeMillis: Long): IncomingRequestCancellation? { return event.getClearContent() .toModel() ?.let { @@ -55,7 +56,7 @@ data class IncomingRequestCancellation( userId = event.senderId, deviceId = it.requestingDeviceId, requestId = it.requestId, - localCreationTimestamp = event.ageLocalTs ?: System.currentTimeMillis() + localCreationTimestamp = event.ageLocalTs ?: 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..0b2c32284b 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 @@ -64,8 +64,9 @@ data class IncomingRoomKeyRequest( * Factory * * @param event the event + * @param currentTimeMillis the current time in milliseconds */ - fun fromEvent(event: Event): IncomingRoomKeyRequest? { + fun fromEvent(event: Event, currentTimeMillis: Long): IncomingRoomKeyRequest? { return event.getClearContent() .toModel() ?.let { @@ -74,7 +75,7 @@ data class IncomingRoomKeyRequest( deviceId = it.requestingDeviceId, requestId = it.requestId, requestBody = it.body ?: RoomKeyRequestBody(), - localCreationTimestamp = event.ageLocalTs ?: System.currentTimeMillis() + localCreationTimestamp = event.ageLocalTs ?: currentTimeMillis ) } } 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 index 5afffef1ae..80f70c83f3 100755 --- 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 @@ -64,8 +64,9 @@ data class IncomingSecretShareRequest( * Factory * * @param event the event + * @param currentTimeMillis the current time in milliseconds */ - fun fromEvent(event: Event): IncomingSecretShareRequest? { + fun fromEvent(event: Event, currentTimeMillis: Long): IncomingSecretShareRequest? { return event.getClearContent() .toModel() ?.let { @@ -74,7 +75,7 @@ data class IncomingSecretShareRequest( deviceId = it.requestingDeviceId, requestId = it.requestId, secretName = it.secretName, - localCreationTimestamp = event.ageLocalTs ?: System.currentTimeMillis() + localCreationTimestamp = event.ageLocalTs ?: currentTimeMillis ) } } 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..ec67e4b31d 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 @@ -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/file/FileService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/file/FileService.kt index 72f8019ada..e3ccbad249 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( 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..759813939f 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 @@ -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/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..57aacc98b8 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 @@ -35,21 +35,21 @@ sealed class 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/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..7790942d84 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 { 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 91% 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..abbdbf8104 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,12 +13,12 @@ * 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 { /** 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 94% 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..270ffb2940 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 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..1f990f4c0a 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,33 +38,13 @@ 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 @@ -84,28 +64,103 @@ interface 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/RoomExtensions.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/RoomExtensions.kt new file mode 100644 index 0000000000..ece9cfbfac --- /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/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/ReferencesAggregatedSummary.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/ReferencesAggregatedSummary.kt index 31ac09efb8..49ba2d5ab6 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 @@ -22,7 +22,6 @@ import org.matrix.android.sdk.api.session.events.model.Content * 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/RoomJoinRulesContent.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/RoomJoinRulesContent.kt index 871b299f93..5237b10d52 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 @@ -36,10 +36,10 @@ 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 + "public" -> RoomJoinRules.PUBLIC + "invite" -> RoomJoinRules.INVITE + "knock" -> RoomJoinRules.KNOCK + "private" -> RoomJoinRules.PRIVATE "restricted" -> RoomJoinRules.RESTRICTED else -> { Timber.w("Invalid value for RoomJoinRules: `$_joinRules`") 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/CallInviteContent.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/call/CallInviteContent.kt index d70e63d122..24c8152f3c 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 @@ -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..e480e013ea 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 @@ -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/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..132b72902f 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 @@ -52,5 +52,5 @@ data class FileInfo( * 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..bd99ea6900 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 @@ -62,5 +62,5 @@ data class ImageInfo( * 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 57% 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..f75704a891 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 */ @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 72% 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..4a4ef46bc8 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,13 +21,21 @@ 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 */ @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, @@ -42,11 +50,11 @@ data class MessageLiveLocationContent( /** * 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/MessageLocationContent.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/MessageLocationContent.kt index 2052133b06..19cb20430d 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 @@ -49,8 +49,8 @@ data class MessageLocationContent( /** * 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/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/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/VideoInfo.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/VideoInfo.kt index 28f3a47d11..b02b4d96ad 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 @@ -67,5 +67,5 @@ data class VideoInfo( * 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/state/StateService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/state/StateService.kt index f645f3ebf9..98171795e2 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 @@ -84,8 +84,9 @@ interface StateService { * @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 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..adbc8ab12a 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 @@ -139,7 +139,7 @@ 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() } } 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..a35a291d9b 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,6 +16,7 @@ package org.matrix.android.sdk.api.session.room.timeline +// TODO Move to internal, strange? data class TimelineEventFilters( /** * A flag to filter edit events 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..b45f3ecb71 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 @@ -31,7 +31,8 @@ data class TimelineSettings( /** * 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, +) { /** * Returns true if this is a thread timeline or false otherwise 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/space/SpaceService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/space/SpaceService.kt index 7826764067..afd26f7be5 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 @@ -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 /** diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/uia/UiaResult.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/uia/UiaResult.kt new file mode 100644 index 0000000000..ee4a180dea --- /dev/null +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/uia/UiaResult.kt @@ -0,0 +1,23 @@ +/* + * 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.uia + +enum class UiaResult { + SUCCESS, + FAILURE, + CANCELLED +} diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/uia/exceptions/UiaCancelledException.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/uia/exceptions/UiaCancelledException.kt new file mode 100644 index 0000000000..d5f9d72efe --- /dev/null +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/uia/exceptions/UiaCancelledException.kt @@ -0,0 +1,19 @@ +/* + * 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.uia.exceptions + +class UiaCancelledException(message: String? = null) : Exception(message) 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..ebad859b05 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 @@ -68,6 +69,14 @@ internal interface AuthAPI { @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. */ 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..590b333e90 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,6 +26,7 @@ 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 @@ -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() { @@ -187,22 +190,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/UIAExt.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/registration/UIAExt.kt index da0866a5fd..7dafacb3c0 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,6 +20,8 @@ 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 @@ -30,14 +32,15 @@ import kotlin.coroutines.suspendCoroutine * @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/crypto/CancelGossipRequestWorker.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/CancelGossipRequestWorker.kt index 98950374ed..aaf23d17b3 100644 --- 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 @@ -32,12 +32,13 @@ 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.util.time.Clock 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) { + SessionSafeCoroutineWorker(context, params, sessionManager, Params::class.java) { @JsonClass(generateAdapter = true) internal data class Params( @@ -65,6 +66,7 @@ internal class CancelGossipRequestWorker(context: Context, params: WorkerParamet @Inject lateinit var sendToDeviceTask: SendToDeviceTask @Inject lateinit var cryptoStore: IMXCryptoStore @Inject lateinit var credentials: Credentials + @Inject lateinit var clock: Clock override fun injectWith(injector: SessionComponent) { injector.inject(this) @@ -85,7 +87,7 @@ internal class CancelGossipRequestWorker(context: Context, params: WorkerParamet content = toDeviceContent.toContent(), senderId = credentials.userId ).also { - it.ageLocalTs = System.currentTimeMillis() + it.ageLocalTs = clock.epochMillis() }) params.recipients.forEach { userToDeviceMap -> 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..73dfc468d9 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 @@ -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..54c9990bf6 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 @@ -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, @@ -701,11 +703,11 @@ 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 { @@ -1022,9 +1024,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 +1034,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") @@ -1224,7 +1226,7 @@ internal class DefaultCryptoService @Inject constructor( // val deviceKey = deviceInfo.identityKey() // // val lastForcedDate = lastNewSessionForcedDates.getObject(senderId, deviceKey) ?: 0 -// val now = System.currentTimeMillis() +// val now = clock.epochMillis() // 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 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..535999373b 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) @@ -310,9 +314,9 @@ internal class DeviceListManager @Inject constructor(private val cryptoStore: IM stored } else { Timber.v("## CRYPTO | downloadKeys() : starts") - val t0 = System.currentTimeMillis() + val t0 = clock.epochMillis() val result = doKeyDownloadForUsers(downloadUsers) - Timber.v("## CRYPTO | downloadKeys() : doKeyDownloadForUsers succeeds after ${System.currentTimeMillis() - t0} ms") + Timber.v("## CRYPTO | downloadKeys() : doKeyDownloadForUsers succeeds after ${clock.epochMillis() - t0} ms") result.also { it.addEntriesFromMap(stored) } @@ -475,8 +479,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 +492,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}") 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..a094189645 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, @@ -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 index 0013c31eea..a2c85e5ceb 100644 --- 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 @@ -26,6 +26,7 @@ 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.UUID import java.util.concurrent.TimeUnit import javax.inject.Inject @@ -44,8 +45,8 @@ internal class GossipingWorkManager @Inject constructor( } // 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() + // The unique queue id will stay the same as long as this object is instantiated + private val queueSuffixApp = UUID.randomUUID() fun postWork(workRequest: OneTimeWorkRequest, policy: ExistingWorkPolicy = ExistingWorkPolicy.APPEND): Cancelable { workManagerProvider.workManager 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 index 3a409cf3fd..1612caba9f 100644 --- 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 @@ -44,6 +44,7 @@ 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.util.time.Clock import org.matrix.android.sdk.internal.worker.WorkerParamsFactory import timber.log.Timber import java.util.concurrent.Executors @@ -59,7 +60,9 @@ internal class IncomingGossipingRequestManager @Inject constructor( private val roomEncryptorsStore: RoomEncryptorsStore, private val roomDecryptorProvider: RoomDecryptorProvider, private val coroutineDispatchers: MatrixCoroutineDispatchers, - private val cryptoCoroutineScope: CoroutineScope) { + private val cryptoCoroutineScope: CoroutineScope, + private val clock: Clock, +) { private val executor = Executors.newSingleThreadExecutor() @@ -89,7 +92,7 @@ internal class IncomingGossipingRequestManager @Inject constructor( fun onVerificationCompleteForDevice(deviceId: String) { // For now we just keep an in memory cache synchronized(recentlyVerifiedDevices) { - recentlyVerifiedDevices[deviceId] = System.currentTimeMillis() + recentlyVerifiedDevices[deviceId] = clock.epochMillis() } } @@ -100,7 +103,7 @@ internal class IncomingGossipingRequestManager @Inject constructor( } if (verifTimestamp == null) return false - val age = System.currentTimeMillis() - verifTimestamp + val age = clock.epochMillis() - verifTimestamp return age < FIVE_MINUTES_IN_MILLIS } @@ -114,11 +117,11 @@ internal class IncomingGossipingRequestManager @Inject constructor( 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 } + // val ageLocalTs = event.unsignedData?.age?.let { clock.epochMillis() - it } when (roomKeyShare?.action) { - GossipingToDeviceObject.ACTION_SHARE_REQUEST -> { + GossipingToDeviceObject.ACTION_SHARE_REQUEST -> { if (event.getClearType() == EventType.REQUEST_SECRET) { - IncomingSecretShareRequest.fromEvent(event)?.let { + IncomingSecretShareRequest.fromEvent(event, clock.epochMillis())?.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") @@ -129,7 +132,7 @@ internal class IncomingGossipingRequestManager @Inject constructor( } } } else if (event.getClearType() == EventType.ROOM_KEY_REQUEST) { - IncomingRoomKeyRequest.fromEvent(event)?.let { + IncomingRoomKeyRequest.fromEvent(event, clock.epochMillis())?.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") @@ -141,7 +144,7 @@ internal class IncomingGossipingRequestManager @Inject constructor( } } GossipingToDeviceObject.ACTION_SHARE_CANCELLATION -> { - IncomingRequestCancellation.fromEvent(event)?.let { + IncomingRequestCancellation.fromEvent(event, clock.epochMillis())?.let { receivedRequestCancellations.add(it) } } @@ -346,7 +349,7 @@ internal class IncomingGossipingRequestManager @Inject constructor( val isDeviceLocallyVerified = cryptoStore.getUserDevice(userId, deviceId)?.trustLevel?.isLocallyVerified() when (secretName) { - MASTER_KEY_SSSS_NAME -> cryptoStore.getCrossSigningPrivateKeys()?.master + 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 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..89e38cb7cf 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,6 +29,7 @@ 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 @@ -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..7eec83abdd 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 @@ -55,7 +56,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() @@ -277,7 +279,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) @@ -348,7 +350,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 +456,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 +522,7 @@ internal class MXOlmDevice @Inject constructor( return MXOutboundSessionInfo( sessionId = sessionId, sharedWithHelper = SharedWithHelper(roomId, sessionId, store), + clock, restoredOutboundGroupSession.creationTime ) } 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..aac6f67aea 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 @@ -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) } } } 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/SendGossipRequestWorker.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/SendGossipRequestWorker.kt index dbdea97411..3b43ad672b 100644 --- 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 @@ -35,13 +35,14 @@ 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.util.time.Clock 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) { + SessionSafeCoroutineWorker(context, params, sessionManager, Params::class.java) { @JsonClass(generateAdapter = true) internal data class Params( @@ -57,6 +58,7 @@ internal class SendGossipRequestWorker(context: Context, params: WorkerParameter @Inject lateinit var sendToDeviceTask: SendToDeviceTask @Inject lateinit var cryptoStore: IMXCryptoStore @Inject lateinit var credentials: Credentials + @Inject lateinit var clock: Clock override fun injectWith(injector: SessionComponent) { injector.inject(this) @@ -85,7 +87,7 @@ internal class SendGossipRequestWorker(context: Context, params: WorkerParameter content = toDeviceContent.toContent(), senderId = credentials.userId ).also { - it.ageLocalTs = System.currentTimeMillis() + it.ageLocalTs = clock.epochMillis() }) params.keyShareRequest.recipients.forEach { userToDeviceMap -> @@ -109,7 +111,7 @@ internal class SendGossipRequestWorker(context: Context, params: WorkerParameter content = toDeviceContent.toContent(), senderId = credentials.userId ).also { - it.ageLocalTs = System.currentTimeMillis() + it.ageLocalTs = clock.epochMillis() }) params.secretShareRequest.recipients.forEach { userToDeviceMap -> 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 index fd472fe73b..113d71d387 100644 --- 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 @@ -34,6 +34,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.crypto.tasks.createUniqueTxnId import org.matrix.android.sdk.internal.session.SessionComponent +import org.matrix.android.sdk.internal.util.time.Clock import org.matrix.android.sdk.internal.worker.SessionSafeCoroutineWorker import org.matrix.android.sdk.internal.worker.SessionWorkerParams import timber.log.Timber @@ -63,6 +64,7 @@ internal class SendGossipWorker( @Inject lateinit var credentials: Credentials @Inject lateinit var messageEncrypter: MessageEncrypter @Inject lateinit var ensureOlmSessionsForDevicesAction: EnsureOlmSessionsForDevicesAction + @Inject lateinit var clock: Clock override fun injectWith(injector: SessionComponent) { injector.inject(this) @@ -129,7 +131,7 @@ internal class SendGossipWorker( content = toDeviceContent.toContent(), senderId = credentials.userId ).also { - it.ageLocalTs = System.currentTimeMillis() + it.ageLocalTs = clock.epochMillis() }) try { 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..86674b4ac5 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 @@ -26,13 +26,17 @@ import org.matrix.android.sdk.internal.crypto.OutgoingGossipingRequestManager 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 -internal class MegolmSessionDataImporter @Inject constructor(private val olmDevice: MXOlmDevice, - private val roomDecryptorProvider: RoomDecryptorProvider, - private val outgoingGossipingRequestManager: OutgoingGossipingRequestManager, - private val cryptoStore: IMXCryptoStore) { +internal class MegolmSessionDataImporter @Inject constructor( + private val olmDevice: MXOlmDevice, + private val roomDecryptorProvider: RoomDecryptorProvider, + private val outgoingGossipingRequestManager: OutgoingGossipingRequestManager, + private val cryptoStore: IMXCryptoStore, + private val clock: Clock, +) { /** * Import a list of megolm session keys. @@ -47,7 +51,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 @@ -103,7 +107,7 @@ 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)") 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..b31b5e8a64 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 @@ -46,6 +46,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 +65,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 @@ -86,11 +88,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 +101,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 +127,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,12 +150,14 @@ 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) } /** @@ -243,12 +247,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() @@ -301,7 +305,7 @@ internal class MXMegolmEncryption( 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 +313,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}>") @@ -334,7 +338,8 @@ internal class MXMegolmEncryption( 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}" }}") + " ${targets.joinToString { "${it.userId}|${it.deviceId}" }}" + ) val withHeldContent = RoomKeyWithHeldContent( roomId = roomId, senderKey = senderKey, 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/olm/MXOlmEncryption.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/algorithms/olm/MXOlmEncryption.kt index 7fdfd5a287..c842c54041 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,7 +33,7 @@ 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. 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..65bbb0c412 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,7 +223,7 @@ internal object MXEncryptedAttachments { ), encryptedByteArray = byteArrayOutputStream.toByteArray() ) - .also { Timber.v("Encrypt in ${System.currentTimeMillis() - t0}ms") } + .also { Timber.v("Encrypt in ${clock.epochMillis() - t0}ms") } } /** @@ -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/keysbackup/KeysBackupPassword.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/keysbackup/KeysBackupPassword.kt index c12879dbee..4d5b38acbf 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 @@ -91,52 +92,53 @@ 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 } 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..d7ce553f39 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 @@ -34,7 +34,7 @@ internal data class OlmSessionWrapper( /** * 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/secrets/DefaultSharedSecretStorageService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/secrets/DefaultSharedSecretStorageService.kt index 19e66635c7..8c877593e7 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 @@ -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 } } @@ -316,7 +316,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 +331,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 { 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..a509315e7a 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 @@ -97,6 +97,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 @@ -110,7 +111,8 @@ 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 { /* ========================================================================================== @@ -307,7 +309,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 +717,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 @@ -791,7 +794,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 @@ -881,7 +884,8 @@ internal class RealmCryptoStore @Inject constructor( try { val key = OlmInboundGroupSessionEntity.createPrimaryKey( olmInboundGroupSessionWrapper.olmInboundGroupSession?.sessionIdentifier(), - olmInboundGroupSessionWrapper.senderKey) + olmInboundGroupSessionWrapper.senderKey + ) it.where() .equalTo(OlmInboundGroupSessionEntityFields.PRIMARY_KEY, key) @@ -1056,13 +1060,16 @@ internal class RealmCryptoStore @Inject constructor( localCreationTimestamp = 0 ) } - return monarchy.findAllPagedWithChanges(realmDataSourceFactory, - LivePagedListBuilder(dataSourceFactory, + return monarchy.findAllPagedWithChanges( + realmDataSourceFactory, + LivePagedListBuilder( + dataSourceFactory, PagedList.Config.Builder() .setPageSize(20) .setEnablePlaceholders(false) .setPrefetchDistance(1) - .build()) + .build() + ) ) } @@ -1071,13 +1078,16 @@ internal class RealmCryptoStore @Inject constructor( realm.where().sort(GossipingEventEntityFields.AGE_LOCAL_TS, Sort.DESCENDING) } val dataSourceFactory = realmDataSourceFactory.map { it.toModel() } - 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 } @@ -1152,7 +1162,7 @@ internal class RealmCryptoStore @Inject constructor( override fun saveGossipingEvents(events: List) { monarchy.writeAsync { realm -> - val now = System.currentTimeMillis() + val now = clock.epochMillis() events.forEach { event -> val ageLocalTs = event.unsignedData?.age?.let { now - it } ?: now val entity = GossipingEventEntity( @@ -1325,7 +1335,7 @@ internal class RealmCryptoStore @Inject constructor( .findAll() .mapNotNull { entity -> when (entity.type) { - GossipRequestType.KEY -> { + GossipRequestType.KEY -> { IncomingRoomKeyRequest( userId = entity.otherUserId, deviceId = entity.otherDeviceId, @@ -1358,7 +1368,7 @@ internal class RealmCryptoStore @Inject constructor( it.otherUserId = request.userId it.requestId = request.requestId ?: "" it.requestState = GossipingRequestState.PENDING - it.localCreationTimestamp = ageLocalTS ?: System.currentTimeMillis() + it.localCreationTimestamp = ageLocalTS ?: clock.epochMillis() if (request is IncomingSecretShareRequest) { it.type = GossipRequestType.SECRET it.requestedInfoStr = request.secretName @@ -1379,7 +1389,7 @@ internal class RealmCryptoStore @Inject constructor( it.otherUserId = request.userId it.requestId = request.requestId ?: "" it.requestState = GossipingRequestState.PENDING - it.localCreationTimestamp = request.localCreationTimestamp ?: System.currentTimeMillis() + it.localCreationTimestamp = request.localCreationTimestamp ?: clock.epochMillis() if (request is IncomingSecretShareRequest) { it.type = GossipRequestType.SECRET it.requestedInfoStr = request.secretName @@ -1535,13 +1545,16 @@ internal class RealmCryptoStore @Inject constructor( it.toOutgoingGossipingRequest() as? OutgoingRoomKeyRequest ?: OutgoingRoomKeyRequest(requestBody = null, requestId = "?", recipients = emptyMap(), state = OutgoingGossipingRequestState.CANCELLED) } - 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 } @@ -1706,7 +1719,7 @@ internal class RealmCryptoStore @Inject constructor( * 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 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..32f24c5d2e 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,10 +33,13 @@ 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.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 @@ -59,7 +62,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() 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/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/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/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/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/verification/DefaultOutgoingSASDefaultVerificationTransaction.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/verification/DefaultOutgoingSASDefaultVerificationTransaction.kt index e203f03b06..e0d912a9a6 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 @@ -123,7 +123,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..d62ca5503d 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 @@ -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 @@ -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, @@ -552,7 +556,8 @@ internal class DefaultVerificationService @Inject constructor( 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) @@ -809,15 +816,19 @@ internal class DefaultVerificationService @Inject constructor( ?.let { vt -> val otherDeviceId = vt.otherDeviceId 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 - ?: "*"))) + 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 ?: "*")) + ) } - outgoingGossipingRequestManager.sendSecretShareRequest(KEYBACKUP_SECRET_SSSS_NAME, mapOf(userId to listOf(otherDeviceId - ?: "*"))) + outgoingGossipingRequestManager.sendSecretShareRequest( + KEYBACKUP_SECRET_SSSS_NAME, mapOf(userId to listOf(otherDeviceId ?: "*")) + ) } } } @@ -917,16 +928,19 @@ internal class DefaultVerificationService @Inject constructor( 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? { @@ -1115,7 +1129,8 @@ internal class DefaultVerificationService @Inject constructor( myDeviceInfoHolder.get().myDevice.fingerprint()!!, txID, otherUserId, - otherDeviceId) + otherDeviceId + ) tx.transport = verificationTransportToDeviceFactory.createTransport(tx) addTransaction(tx) @@ -1150,7 +1165,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, @@ -1175,11 +1190,13 @@ internal class DefaultVerificationService @Inject constructor( 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) @@ -1228,7 +1245,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 +1271,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 +1290,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 + ) + ) } } @@ -1307,7 +1328,8 @@ internal class DefaultVerificationService @Inject constructor( myDeviceInfoHolder.get().myDevice.fingerprint()!!, transactionId, otherUserId, - otherDeviceId) + otherDeviceId + ) tx.transport = verificationTransportRoomMessageFactory.createTransport(roomId, tx) addTransaction(tx) @@ -1333,7 +1355,8 @@ internal class DefaultVerificationService @Inject constructor( otherUserId, existingRequest.requestInfo?.fromDevice ?: "", existingRequest.requestInfo?.methods, - methods) { + methods + ) { verificationTransportRoomMessageFactory.createTransport(roomId, it) } if (methods.isNullOrEmpty()) { @@ -1343,7 +1366,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 +1396,8 @@ internal class DefaultVerificationService @Inject constructor( otherUserId, existingRequest.requestInfo?.fromDevice ?: "", existingRequest.requestInfo?.methods, - methods) { + methods + ) { verificationTransportToDeviceFactory.createTransport(it) } if (methods.isNullOrEmpty()) { @@ -1446,7 +1471,8 @@ internal class DefaultVerificationService @Inject constructor( qrCodeData = qrCodeData, userId = userId, deviceId = deviceId ?: "", - isIncoming = false) + isIncoming = false + ) tx.transport = transportCreator.invoke(tx) 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 index a763c05e07..0a175ae3ca 100644 --- 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 @@ -35,7 +35,7 @@ import javax.inject.Inject * Possible next worker : None */ internal class SendVerificationMessageWorker(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/crypto/verification/VerificationMessageProcessor.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/verification/VerificationMessageProcessor.kt index 52166761ab..ec4e1aa65c 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 @@ -34,11 +34,13 @@ 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 clock: Clock, private val verificationService: DefaultVerificationService, @UserId private val userId: String, @DeviceId private val deviceId: String? @@ -71,8 +73,7 @@ internal class VerificationMessageProcessor @Inject constructor( // 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 { + if (!VerificationService.isValidRequest(event.ageLocalTs ?: event.originServerTs, clock.epochMillis())) return Unit.also { Timber.d("## SAS Verification live observer: msgId: ${event.eventId} is outdated") } 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..325a6f0ba2 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 @@ -47,6 +47,7 @@ import org.matrix.android.sdk.internal.crypto.model.rest.VERIFICATION_METHOD_REC 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.session.room.send.LocalEchoEventFactory +import org.matrix.android.sdk.internal.util.time.Clock import org.matrix.android.sdk.internal.worker.SessionSafeCoroutineWorker import org.matrix.android.sdk.internal.worker.WorkerParamsFactory import timber.log.Timber @@ -61,7 +62,8 @@ internal class VerificationTransportRoomMessage( private val roomId: String, private val localEchoEventFactory: LocalEchoEventFactory, private val tx: DefaultVerificationTransaction?, - private val coroutineScope: CoroutineScope + private val coroutineScope: CoroutineScope, + private val clock: Clock, ) : VerificationTransport { override fun sendToOther(type: String, @@ -77,10 +79,12 @@ internal class VerificationTransportRoomMessage( content = verificationInfo.toEventContent()!! ) - val workerParams = WorkerParamsFactory.toData(SendVerificationMessageWorker.Params( - sessionId = sessionId, - eventId = event.eventId ?: "" - )) + 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, @@ -155,7 +159,7 @@ internal class VerificationTransportRoomMessage( transactionId = "", fromDevice = userDeviceId ?: "", methods = supportedMethods, - timestamp = System.currentTimeMillis() + timestamp = clock.epochMillis() ) val info = MessageVerificationRequestContent( @@ -175,10 +179,12 @@ internal class VerificationTransportRoomMessage( content ) - val workerParams = WorkerParamsFactory.toData(SendVerificationMessageWorker.Params( - sessionId = sessionId, - eventId = event.eventId ?: "" - )) + val workerParams = WorkerParamsFactory.toData( + SendVerificationMessageWorker.Params( + sessionId = sessionId, + eventId = event.eventId ?: "" + ) + ) val workRequest = workManagerProvider.matrixOneTimeWorkRequestBuilder() .setConstraints(WorkManagerProvider.workConstraints) @@ -230,10 +236,12 @@ internal class VerificationTransportRoomMessage( roomId = roomId, content = MessageVerificationCancelContent.create(transactionId, code).toContent() ) - val workerParams = WorkerParamsFactory.toData(SendVerificationMessageWorker.Params( - sessionId = sessionId, - eventId = event.eventId ?: "" - )) + val workerParams = WorkerParamsFactory.toData( + SendVerificationMessageWorker.Params( + sessionId = sessionId, + eventId = event.eventId ?: "" + ) + ) enqueueSendWork(workerParams) } @@ -250,10 +258,12 @@ internal class VerificationTransportRoomMessage( ) ).toContent() ) - val workerParams = WorkerParamsFactory.toData(SendVerificationMessageWorker.Params( - sessionId = sessionId, - eventId = event.eventId ?: "" - )) + val workerParams = WorkerParamsFactory.toData( + SendVerificationMessageWorker.Params( + sessionId = sessionId, + eventId = event.eventId ?: "" + ) + ) val enqueueInfo = enqueueSendWork(workerParams) val workLiveData = workManagerProvider.workManager @@ -296,13 +306,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 +371,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..b1b7ad7a98 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 @@ -22,6 +22,7 @@ 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( @@ -33,17 +34,21 @@ internal class VerificationTransportRoomMessageFactory @Inject constructor( @DeviceId private val deviceId: String?, private val localEchoEventFactory: LocalEchoEventFactory, - private val taskExecutor: TaskExecutor + private val taskExecutor: TaskExecutor, + private val clock: Clock, ) { fun createTransport(roomId: String, tx: DefaultVerificationTransaction?): VerificationTransportRoomMessage { - return VerificationTransportRoomMessage(workManagerProvider, + return VerificationTransportRoomMessage( + workManagerProvider, sessionId, userId, deviceId, roomId, localEchoEventFactory, tx, - taskExecutor.executorScope) + taskExecutor.executorScope, + 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/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/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..24ac310653 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,7 @@ 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.util.Normalizer import timber.log.Timber import javax.inject.Inject @@ -58,7 +59,7 @@ internal class RealmSessionStoreMigration @Inject constructor( override fun equals(other: Any?) = other is RealmSessionStoreMigration override fun hashCode() = 1000 - val schemaVersion = 26L + val schemaVersion = 27L override fun migrate(realm: DynamicRealm, oldVersion: Long, newVersion: Long) { Timber.d("Migrating Realm Session from $oldVersion to $newVersion") @@ -89,5 +90,6 @@ 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() } } 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..d052a7dea4 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 @@ -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, @@ -258,8 +269,8 @@ private fun HashMap.addSenderState(realm: Realm, roo /** * 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,15 +279,17 @@ 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) } } 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/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/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/MigrateSessionTo027.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/migration/MigrateSessionTo027.kt new file mode 100644 index 0000000000..fdd8c46d02 --- /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/model/ChunkEntity.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/ChunkEntity.kt index 88eb821aa9..822bc1bd8f 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" 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..c3abd8b028 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,7 +28,8 @@ 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() { /** 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/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..9a92b14510 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,6 +17,7 @@ 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 @@ -47,6 +48,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, 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/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..1376839f93 --- /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/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/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/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..215ab34f95 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/network/NetworkConnectivityChecker.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/network/NetworkConnectivityChecker.kt index cd7c99b8f9..3d2b2bfbfb 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 @@ -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()) 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/session/DefaultFileService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/DefaultFileService.kt index ac097f57ee..78f1c84f3d 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 @@ -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 ) } } 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/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/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/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/UploadContentWorker.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/content/UploadContentWorker.kt index 75606f2e7a..75a79abcdb 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 @@ -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()) } @@ -329,7 +331,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/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/initsync/DefaultSyncStatusService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/initsync/DefaultSyncStatusService.kt index 4a1e6661b0..c138c1a40e 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() 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..1b96931c6c 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 } 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..d20cf8f140 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 @@ -72,6 +72,7 @@ internal class ViaParameterFinder @Inject constructor( */ 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/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/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/api/pushrules/rest/GetPushRulesResponse.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/pushers/GetPushRulesResponse.kt similarity index 90% 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..de03819629 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. 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..dab6d37317 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 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..ff685e9281 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 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 95% 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..91d092a2b4 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 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..7e0b44a314 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 ) @@ -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") } } @@ -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..65ef94999f 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 @@ -194,7 +194,8 @@ 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 @@ -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 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/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/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/joining/JoinRoomTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/membership/joining/JoinRoomTask.kt index f883cc33ec..6306f3c6fe 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 @@ -35,6 +35,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 @@ -53,7 +54,8 @@ internal class DefaultJoinRoomTask @Inject constructor( @SessionDatabase private val realmConfiguration: RealmConfiguration, 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 +92,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 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..8d35a8fea4 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 - 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) { 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..ecc8149255 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 @@ -39,7 +39,7 @@ import javax.inject.Inject * 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/RedactEventWorker.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/RedactEventWorker.kt index c03d1fa81e..83c61d2845 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 @@ -34,7 +34,7 @@ import javax.inject.Inject * 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( 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..ddbe8a61a0 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 @@ -40,7 +40,7 @@ import javax.inject.Inject * 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/queue/EventSenderProcessorCoroutine.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/queue/EventSenderProcessorCoroutine.kt index 5b4efa5df6..a1d3e2c0ac 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 @@ -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/state/DefaultStateService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/state/DefaultStateService.kt index 89d33f98d2..e5c7a75cb6 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 { @@ -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..1f2ec09367 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 @@ -51,7 +51,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..52879d7121 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 @@ -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/timeline/DefaultTimeline.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/DefaultTimeline.kt index 5064ebf49b..18fbe73c09 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 @@ -43,28 +43,32 @@ import org.matrix.android.sdk.internal.session.sync.handler.room.ReadReceiptHand 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, + 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,6 +104,7 @@ internal class DefaultTimeline(private val roomId: String, threadsAwarenessHandler = threadsAwarenessHandler, lightweightSettingsStorage = lightweightSettingsStorage, onEventsUpdated = this::sendSignalToPostSnapshot, + onEventsDeleted = this::onEventsDeleted, onLimitedTimeline = this::onLimitedTimeline, onNewTimelineEvents = this::onNewTimelineEvents ) @@ -304,6 +309,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 +374,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..849d7bd7ab 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 @@ -34,6 +34,7 @@ import org.matrix.android.sdk.internal.session.room.membership.LoadRoomMembersTa import org.matrix.android.sdk.internal.session.room.relation.threads.FetchThreadTimelineTask 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,8 +51,9 @@ internal class DefaultTimelineService @AssistedInject constructor( private val lightweightSettingsStorage: LightweightSettingsStorage, private val readReceiptHandler: ReadReceiptHandler, private val coroutineDispatchers: MatrixCoroutineDispatchers, - private val timelineEventDataSource: TimelineEventDataSource -) : TimelineService { + private val timelineEventDataSource: TimelineEventDataSource, + private val clock: Clock, + ) : TimelineService { @AssistedFactory interface Factory { @@ -75,7 +77,8 @@ internal class DefaultTimelineService @AssistedInject constructor( readReceiptHandler = readReceiptHandler, getEventTask = contextOfEventTask, threadsAwarenessHandler = threadsAwarenessHandler, - lightweightSettingsStorage = lightweightSettingsStorage + lightweightSettingsStorage = lightweightSettingsStorage, + clock = clock ) } 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/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..bcf202962c 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 @@ -42,6 +42,7 @@ import org.matrix.android.sdk.internal.database.query.findLastForwardChunkOfThre 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.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 +54,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,6 +98,7 @@ internal class LoadTimelineStrategy( val threadsAwarenessHandler: ThreadsAwarenessHandler, val lightweightSettingsStorage: LightweightSettingsStorage, val onEventsUpdated: (Boolean) -> Unit, + val onEventsDeleted: () -> Unit, val onLimitedTimeline: () -> Unit, val onNewTimelineEvents: (List) -> Unit ) @@ -152,7 +156,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, @@ -302,7 +306,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..27f4245b22 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) @@ -505,6 +508,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 +551,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..e5dd8aab30 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 } 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..828e01955a 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 @@ -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/search/SearchTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/search/SearchTask.kt index 3ba7d11c3d..fcaf3b60a7 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 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 355039b22c..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 @@ -26,6 +26,7 @@ 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 @@ -258,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( @@ -276,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/sync/InitialSyncStatusRepository.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/InitialSyncStatusRepository.kt index bd20ada28b..8e0c3422b9 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 @@ -46,7 +47,10 @@ internal interface InitialSyncStatusRepository { /** * 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 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..42cd972e0c 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 @@ -34,11 +34,12 @@ internal enum class SyncPresence(val value: String) { companion object { fun from(presenceEnum: PresenceEnum): SyncPresence { return when (presenceEnum) { - PresenceEnum.ONLINE -> Online - PresenceEnum.OFFLINE -> Offline + PresenceEnum.ONLINE -> Online + PresenceEnum.OFFLINE -> Offline 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/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..5437a015fd 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,12 +203,12 @@ 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 @@ -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) @@ -389,8 +393,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 +427,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 +444,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 @@ -475,7 +484,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 +515,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/job/SyncWorker.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/job/SyncWorker.kt index c67c0e350e..f183c4cb28 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 @@ -42,7 +42,7 @@ private const val DEFAULT_DELAY_MILLIS = 30_000L * 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/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/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/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/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..9f42688f7d 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) 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/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/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/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/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/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/MatrixWorkerFactory.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/worker/MatrixWorkerFactory.kt index 0b451e9c34..e56b359f7a 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 @@ -93,7 +93,7 @@ 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, 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/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 95% 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..95787173da 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 @@ -148,14 +149,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 diff --git a/tools/check/forbidden_strings_in_code.txt b/tools/check/forbidden_strings_in_code.txt index 21ab0bab77..393e942b2a 100755 --- a/tools/check/forbidden_strings_in_code.txt +++ b/tools/check/forbidden_strings_in_code.txt @@ -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/vector-config/src/main/res/values/config-settings.xml b/vector-config/src/main/res/values/config-settings.xml index b6d1c6676c..0a4da4c98e 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,10 +32,10 @@ - - + true + false diff --git a/vector/build.gradle b/vector/build.gradle index 0104f71f3b..162d9fe81c 100644 --- a/vector/build.gradle +++ b/vector/build.gradle @@ -18,7 +18,7 @@ ext.versionMinor = 4 // Note: even values are reserved for regular release, odd values for hotfix release. // When creating a hotfix, you should decrease the value, since the current value // is the value for the next regular release. -ext.versionPatch = 13 +ext.versionPatch = 14 static def getGitTimestamp() { def cmd = 'git show -s --format=%ct' @@ -368,7 +368,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.47' // FlowBinding implementation libs.github.flowBinding 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/VerifySessionInteractiveTest.kt b/vector/src/androidTest/java/im/vector/app/VerifySessionInteractiveTest.kt index c82b543a08..76f09638be 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() diff --git a/vector/src/androidTest/java/im/vector/app/VerifySessionPassphraseTest.kt b/vector/src/androidTest/java/im/vector/app/VerifySessionPassphraseTest.kt index 80d8315a0e..76d5717000 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() 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/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..07b7c9ebf9 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 @@ -59,6 +60,8 @@ class DebugMenuActivity : VectorBaseActivity() { @Inject lateinit var activeSessionHolder: ActiveSessionHolder + @Inject + lateinit var clock: Clock private lateinit var buffer: ByteArray @@ -165,7 +168,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 +183,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 +200,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 +214,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) 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/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..f8c30f813d 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 { 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..ec1b9ca7a2 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,7 +49,7 @@ 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()) { 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..8917513537 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) 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..029fce7423 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)) { @@ -158,8 +162,9 @@ class VectorDateFormatter @Inject constructor(private val context: Context, 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/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/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/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..757b415ce5 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) 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/extensions/Session.kt b/vector/src/main/java/im/vector/app/core/extensions/Session.kt index 87ed51522f..9bc1aff868 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() } @@ -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/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/VectorGlideModelLoader.kt b/vector/src/main/java/im/vector/app/core/glide/VectorGlideModelLoader.kt index 6b42e3fff8..c53db12b6b 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() 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..7bedeaa4ff 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() 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/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..febcfc5ef2 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 @@ -266,9 +268,27 @@ abstract class VectorBaseActivity : AppCompatActivity(), Maver is GlobalError.CertificateError -> handleCertificateError(globalError) GlobalError.ExpiredAccount -> Unit // TODO Handle account expiration + is GlobalError.InitialSyncRequest -> handleInitialSyncRequest(globalError) } } + 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() 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..7db35a8e8f 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 @@ -83,8 +83,8 @@ abstract class VectorBaseFragment : Fragment(), MavericksView * [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 * ========================================================================================== */ 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..2b47412901 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 @@ -26,4 +26,4 @@ interface VectorSharedAction * 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/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/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/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/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/ui/bottomsheet/BottomSheetGenericController.kt b/vector/src/main/java/im/vector/app/core/ui/bottomsheet/BottomSheetGenericController.kt index 6e92549809..70faa87645 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 @@ -21,7 +21,7 @@ import com.airbnb.epoxy.TypedEpoxyController * 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/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/PasswordStrengthBar.kt b/vector/src/main/java/im/vector/app/core/ui/views/PasswordStrengthBar.kt index a984707bf7..39bfce6975 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 @@ -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/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/utils/ExternalApplicationsUtil.kt b/vector/src/main/java/im/vector/app/core/utils/ExternalApplicationsUtil.kt index a9375b6545..d961dcaa46 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 @@ -147,8 +147,11 @@ fun openFileSelection(activity: Activity, * 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) @@ -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) { @@ -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/features/MainActivity.kt b/vector/src/main/java/im/vector/app/features/MainActivity.kt index 42bd2318b3..121edd4216 100644 --- a/vector/src/main/java/im/vector/app/features/MainActivity.kt +++ b/vector/src/main/java/im/vector/app/features/MainActivity.kt @@ -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..9d54475e8c 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 = false } 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..ec34ff7421 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 @@ -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/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/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..06cefa702e 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 @@ -49,11 +49,6 @@ data class JoinedRoom( */ Invite, - /** - * Room joined via space explore - */ - MobileExploreRooms, - /** * Room joined via link */ 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/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/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/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/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(), CallContro @Inject lateinit var callManager: WebRtcCallManager @Inject lateinit var avatarRenderer: AvatarRenderer + @Inject lateinit var screenCaptureServiceConnection: ScreenCaptureServiceConnection private val callViewModel: VectorCallViewModel by viewModel() @@ -512,20 +516,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 +634,32 @@ class VectorCallActivity : VectorBaseActivity(), CallContro } } + private val screenSharingPermissionActivityResultLauncher = registerStartForActivityResult { activityResult -> + if (activityResult.resultCode == Activity.RESULT_OK) { + callViewModel.handle(VectorCallViewActions.StartScreenSharing) + // We need to start a foreground service with a sticky notification during screen sharing + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { + ContextCompat.startForegroundService( + this, + Intent(this, ScreenCaptureService::class.java) + ) + screenCaptureServiceConnection.bind() + } + } + } + + 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/VectorCallViewActions.kt b/vector/src/main/java/im/vector/app/features/call/VectorCallViewActions.kt index d1ed961814..c84f733b9a 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 @@ -40,4 +40,6 @@ sealed class VectorCallViewActions : VectorViewModelAction { object CallTransferSelectionCancelled : VectorCallViewActions() data class CallTransferSelectionResult(val callTransferResult: CallTransferResult) : VectorCallViewActions() object TransferCall : VectorCallViewActions() + object ToggleScreenSharing : VectorCallViewActions() + object StartScreenSharing : 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..55a0219bfe 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 @@ -256,27 +257,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 +290,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 +313,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 +338,38 @@ 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() + setState { + copy(isSharingScreen = true) + } + } + } + } + + private fun handleToggleScreenSharing(isSharingScreen: Boolean) { + if (isSharingScreen) { + call?.stopSharingScreen() + setState { + copy(isSharingScreen = false) + } + _viewEvents.post( + VectorCallViewEvents.StopScreenSharingService + ) + } 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/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..922e9676a8 --- /dev/null +++ b/vector/src/main/java/im/vector/app/features/call/webrtc/ScreenCaptureServiceConnection.kt @@ -0,0 +1,54 @@ +/* + * Copyright (c) 2022 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package im.vector.app.features.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 { + + private var isBound = false + private var screenCaptureService: ScreenCaptureService? = null + + fun bind() { + if (!isBound) { + 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 + } + + override fun onServiceDisconnected(className: ComponentName) { + isBound = false + screenCaptureService = 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..f0db3e199f 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 @@ -670,15 +670,11 @@ 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.direction = direction @@ -770,6 +766,14 @@ class WebRtcCall( return currentCaptureFormat } + fun startSharingScreen() { + // TODO. Will be handled within the next PR. + } + + fun stopSharingScreen() { + // TODO. Will be handled within the next PR. + } + private suspend fun release() { listeners.clear() mxCall.removeListener(this) @@ -933,11 +937,6 @@ class WebRtcCall( wasLocalOnHold = nowOnHold if (prevOnHold != nowOnHold) { isLocalOnHold = nowOnHold - if (nowOnHold) { - timer.pause() - } else { - timer.resume() - } listeners.forEach { tryOrNull { it.onHoldUnhold() } } @@ -981,7 +980,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/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/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..6cfe0bf686 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 @@ -170,7 +170,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( @@ -252,7 +252,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/settings/KeysBackupSettingsFragment.kt b/vector/src/main/java/im/vector/app/features/crypto/keysbackup/settings/KeysBackupSettingsFragment.kt index 50d5e56483..4d3ec9a820 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 @@ -32,7 +32,7 @@ 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 { 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/quads/SharedSecureStorageViewModel.kt b/vector/src/main/java/im/vector/app/features/crypto/quads/SharedSecureStorageViewModel.kt index 51d8b3a8d5..445cc70527 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 @@ -76,7 +76,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,8 +87,8 @@ class SharedSecureStorageViewModel @AssistedInject constructor( setState { copy(userId = session.myUserId) } - val integrityResult = session.sharedSecretStorageService.checkShouldBeAbleToAccessSecrets(initialState.requestedSecrets, initialState.keyId) - if (integrityResult !is IntegrityResult.Success) { + 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), @@ -96,8 +96,8 @@ class SharedSecureStorageViewModel @AssistedInject constructor( ) ) } - 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 +134,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 +205,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))) @@ -228,7 +228,7 @@ class SharedSecureStorageViewModel @AssistedInject constructor( withContext(Dispatchers.IO) { initialState.requestedSecrets.forEach { if (session.accountDataService().getUserAccountDataEvent(it) != null) { - val res = session.sharedSecretStorageService.getSecret( + val res = session.sharedSecretStorageService().getSecret( name = it, keyId = keyInfo.id, secretKey = keySpec) @@ -243,7 +243,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)) @@ -262,7 +262,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")) @@ -297,7 +297,7 @@ class SharedSecureStorageViewModel @AssistedInject constructor( withContext(Dispatchers.IO) { initialState.requestedSecrets.forEach { if (session.accountDataService().getUserAccountDataEvent(it) != null) { - val res = session.sharedSecretStorageService.getSecret( + val res = session.sharedSecretStorageService().getSecret( name = it, keyId = keyInfo.id, secretKey = keySpec) @@ -312,7 +312,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..2092fe0f00 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 @@ -61,7 +61,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 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..57a8ad68b7 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 -> { 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..753e9f1942 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,7 +117,7 @@ class BootstrapCrossSigningTask @Inject constructor( val keyInfo: SsssKeyCreationInfo - val ssssService = session.sharedSecretStorageService + val ssssService = session.sharedSecretStorageService() params.progressListener?.onProgress( WaitingViewData( 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..a85c318a29 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, 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/verification/IncomingVerificationRequestHandler.kt b/vector/src/main/java/im/vector/app/features/crypto/verification/IncomingVerificationRequestHandler.kt index 3a3f1054f1..95378860e7 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,7 +160,12 @@ 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 + ) } } } @@ -168,7 +177,7 @@ class IncomingVerificationRequestHandler @Inject constructor( } 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/VerificationBottomSheetViewModel.kt b/vector/src/main/java/im/vector/app/features/crypto/verification/VerificationBottomSheetViewModel.kt index b389a831da..a7998dc474 100644 --- a/vector/src/main/java/im/vector/app/features/crypto/verification/VerificationBottomSheetViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/crypto/verification/VerificationBottomSheetViewModel.kt @@ -53,6 +53,7 @@ import org.matrix.android.sdk.api.session.crypto.verification.VerificationServic 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.LocalEcho +import org.matrix.android.sdk.api.session.getUser import org.matrix.android.sdk.api.util.MatrixItem import org.matrix.android.sdk.api.util.awaitCallback import org.matrix.android.sdk.api.util.fromBase64 @@ -149,7 +150,7 @@ class VerificationBottomSheetViewModel @AssistedInject constructor( pendingRequest = if (pr != null) Success(pr) else Uninitialized, isMe = initialState.otherUserId == session.myUserId, currentDeviceCanCrossSign = session.cryptoService().crossSigningService().canCrossSign(), - quadSContainsSecrets = session.sharedSecretStorageService.isRecoverySetup(), + quadSContainsSecrets = session.sharedSecretStorageService().isRecoverySetup(), hasAnyOtherSession = hasAnyOtherSession ) } @@ -231,7 +232,7 @@ class VerificationBottomSheetViewModel @AssistedInject constructor( override fun handle(action: VerificationAction) = withState { state -> 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 +245,7 @@ class VerificationBottomSheetViewModel @AssistedInject constructor( ) } viewModelScope.launch { - val result = runCatching { session.createDirectRoom(otherUserId) } + val result = runCatching { session.roomService().createDirectRoom(otherUserId) } result.fold( { data -> setState { @@ -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), 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..4b59e2e6fb 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 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/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/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/home/HomeActivity.kt b/vector/src/main/java/im/vector/app/features/home/HomeActivity.kt index 7a8dcf2634..12ae67fe6e 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 @@ -243,7 +245,7 @@ class HomeActivity : } if (args?.inviteNotificationRoomId != null) { activeSessionHolder.getSafeActiveSession()?.permalinkService()?.createPermalink(args.inviteNotificationRoomId)?.let { - navigator.openMatrixToBottomSheet(this, it) + navigator.openMatrixToBottomSheet(this, it, OriginOfMatrixTo.NOTIFICATION) } } @@ -370,7 +372,7 @@ class HomeActivity : } views.waitingView.root.isVisible = true } - else -> { + else -> { // Idle or Incremental sync status views.waitingView.root.isVisible = false } @@ -480,7 +482,7 @@ class HomeActivity : activeSessionHolder.getSafeActiveSession() ?.permalinkService() ?.createPermalink(parcelableExtra.inviteNotificationRoomId)?.let { - navigator.openMatrixToBottomSheet(this, it) + navigator.openMatrixToBottomSheet(this, it, OriginOfMatrixTo.NOTIFICATION) } } handleIntent(intent) @@ -567,14 +569,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 +610,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/HomeActivityViewModel.kt b/vector/src/main/java/im/vector/app/features/home/HomeActivityViewModel.kt index c8b0fa024e..f0e27e2ee7 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 { @@ -215,13 +216,16 @@ 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 { + val knownRooms = activeSessionHolder.getSafeActiveSession() + ?.roomService() + ?.getRoomSummaries(roomSummaryQueryParams { memberships = Membership.activeMemberships() })?.size ?: 0 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..fc39165a7e 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 @@ -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/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/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/TimelineFragment.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/TimelineFragment.kt index 32f0e46eac..099f3779ee 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 @@ -119,8 +119,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 +296,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 { @@ -1445,7 +1443,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) } @@ -1730,14 +1728,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 +2186,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 { 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..7b46eed4f8 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() } @@ -248,7 +252,7 @@ class TimelineViewModel @AssistedInject constructor( prepareToEncrypt = Loading() viewModelScope.launch { runCatching { - room.prepareToEncrypt() + room.roomCryptoService().prepareToEncrypt() }.fold({ prepareToEncrypt = Success(Unit) }, { @@ -352,7 +356,7 @@ class TimelineViewModel @AssistedInject constructor( private fun markThreadTimelineAsReadLocal() { initialState.rootThreadEventId?.let { session.coroutineScope.launch { - room.markThreadAsRead(it) + room.threadsLocalService().markThreadAsRead(it) } } } @@ -488,7 +492,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,7 +509,7 @@ 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)) } } @@ -517,7 +521,7 @@ class TimelineViewModel @AssistedInject constructor( 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 +641,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 +654,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 +686,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 +740,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 +812,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,14 +823,23 @@ 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 && @@ -891,8 +904,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 +921,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 +937,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 +969,7 @@ class TimelineViewModel @AssistedInject constructor( } bufferedMostRecentDisplayedEvent.root.eventId?.let { eventId -> session.coroutineScope.launch { - tryOrNull { room.setReadReceipt(eventId) } + tryOrNull { room.readService().setReadReceipt(eventId) } } } } @@ -973,14 +986,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 +1009,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) @@ -1070,13 +1083,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 +1099,7 @@ class TimelineViewModel @AssistedInject constructor( copy(syncState = syncState) } - session.getSyncStatusLive() + session.syncStatusService().getSyncStatusLive() .asFlow() .filterIsInstance() .setOnEach { @@ -1190,7 +1203,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 +1267,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/MessageComposerViewModel.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/composer/MessageComposerViewModel.kt index 9c81a39941..8351af14dc 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 @@ -202,12 +207,12 @@ class MessageComposerViewModel @AssistedInject constructor( 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) } else { - room.sendTextMessage(action.text, autoMarkdown = action.autoMarkdown) + room.sendService().sendTextMessage(action.text, autoMarkdown = action.autoMarkdown) } _viewEvents.post(MessageComposerViewEvents.MessageSent) @@ -228,12 +233,12 @@ 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) } else { - room.sendTextMessage(parsedCommand.message, autoMarkdown = false) + room.sendService().sendTextMessage(parsedCommand.message, autoMarkdown = false) } _viewEvents.post(MessageComposerViewEvents.MessageSent) popDraft() @@ -283,13 +288,16 @@ 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) } 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 +305,12 @@ 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)) } else { - room.sendFormattedTextMessage(message, rainbowGenerator.generate(message)) + room.sendService().sendFormattedTextMessage(message, rainbowGenerator.generate(message)) } _viewEvents.post(MessageComposerViewEvents.SlashCommandResultOk(parsedCommand)) popDraft() @@ -310,13 +318,13 @@ 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)) } 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,12 +334,12 @@ 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) } else { - room.sendFormattedTextMessage( + room.sendService().sendFormattedTextMessage( text, formattedText) } @@ -374,7 +382,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 +453,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,13 +494,13 @@ 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) @@ -504,7 +512,7 @@ class MessageComposerViewModel @AssistedInject constructor( popDraft() } is SendMode.Quote -> { - room.sendQuotedTextMessage( + room.sendService().sendQuotedTextMessage( quotedEvent = state.sendMode.timelineEvent, text = action.text.toString(), autoMarkdown = action.autoMarkdown, @@ -518,12 +526,12 @@ 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( + } ?: room.relationService().replyToMessage( eventReplied = timelineEvent, replyText = action.text.toString(), autoMarkdown = action.autoMarkdown, @@ -549,13 +557,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 +594,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() } } } @@ -600,22 +608,22 @@ class MessageComposerViewModel @AssistedInject constructor( ChatEffect.CONFETTI -> R.string.default_message_emote_confetti ChatEffect.SNOWFALL -> R.string.default_message_emote_snow }) - room.sendTextMessage(defaultMessage, MessageType.MSGTYPE_EMOTE) + 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 +653,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 +678,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 +699,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 +740,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 +757,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 +781,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,8 +798,8 @@ class MessageComposerViewModel @AssistedInject constructor( } } rootThreadEventId?.let { - room.replyInThread(it, sequence) - } ?: room.sendTextMessage(sequence) + room.relationService().replyInThread(it, sequence) + } ?: room.sendService().sendTextMessage(sequence) } /** @@ -802,19 +810,19 @@ class MessageComposerViewModel @AssistedInject constructor( 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,7 +843,7 @@ 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(), @@ -902,7 +910,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..016a39d919 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,6 +21,7 @@ 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: @@ -35,7 +36,7 @@ sealed interface SendMode { 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/readreceipts/DisplayReadReceiptsController.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/readreceipts/DisplayReadReceiptsController.kt index bf88218fa6..c316c556b0 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 @@ -31,7 +31,7 @@ import javax.inject.Inject 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..c11fa276f6 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 @@ -134,7 +135,16 @@ class SearchFragment @Inject constructor( roomEncryptionTrustLevel = null, 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..c77cdceed0 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 @@ -109,7 +111,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_() @@ -125,7 +127,8 @@ class SearchResultController @Inject constructor( .formattedDate(dateFormatter.format(event.originServerTs, DateFormatKind.MESSAGE_SIMPLE)) .spannable(spannable.toEpoxyCharSequence()) .sender(eventAndSender.sender - ?: eventAndSender.event.senderId?.let { session.getRoomMember(it, data.roomId) }?.toMatrixItem()) + ?: eventAndSender.event.senderId?.let { session.roomService().getRoomMember(it, data.roomId) }?.toMatrixItem() + ) .threadDetails(event.threadDetails) .threadSummaryFormatted(displayableEventFormatter.formatThreadSummary(event.threadDetails?.threadSummaryLatestEvent).toString()) .areThreadMessagesEnabled(userPreferencesProvider.areThreadMessagesEnabled()) 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..6c9f7ac4ff 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,19 +79,21 @@ 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 { /** @@ -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) { 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 99c36d1190..df2a1fbe81 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 @@ -454,7 +455,7 @@ class MessageActionsViewModel @AssistedInject constructor( // if (!vectorPreferences.areThreadMessagesEnabled()) return false // Disable beta prompt if the homeserver do not support threads if (!vectorPreferences.areThreadMessagesEnabled() && - !session.getHomeServerCapabilities().canUseThreading) return false + !session.homeServerCapabilitiesService().getHomeServerCapabilities().canUseThreading) return false if (initialState.isFromThreadTimeline) return false if (event.root.isThread()) return false 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..f96ee7eee2 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 @@ -49,7 +50,8 @@ 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..080a79829a 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 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..edd2271550 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,17 +663,17 @@ 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) { @@ -681,19 +684,19 @@ class MessageItemFactory @Inject constructor( // nop } }, - editStart, - editEnd, - Spanned.SPAN_INCLUSIVE_EXCLUSIVE) + 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 +709,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 +734,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/VerificationItemFactory.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/VerificationItemFactory.kt index fb8f8fce9d..ecd80297fc 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,6 +32,8 @@ 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 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/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/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/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/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/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 false - msgType == MessageType.MSGTYPE_LOCATION -> vectorPreferences.labsRenderLocationsInTimeline() + this == null || msgType == MessageType.MSGTYPE_BEACON_INFO -> false + msgType == MessageType.MSGTYPE_LOCATION -> vectorPreferences.labsRenderLocationsInTimeline() else -> msgType in MSG_TYPES_WITH_TIMESTAMP_INSIDE_MESSAGE } } 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..0e4aebecfc 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() 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/RoomWidgetsController.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/widget/RoomWidgetsController.kt index b2da3bfc78..87392c5d7a 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 @@ -32,7 +32,7 @@ import javax.inject.Inject 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..426ceb5024 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() { 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..0a31987ae5 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( @@ -252,7 +252,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 +262,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 +272,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/RoomListViewModel.kt b/vector/src/main/java/im/vector/app/features/home/room/list/RoomListViewModel.kt index 70974bc1f6..9fd6de1a74 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 @@ -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