diff --git a/.editorconfig b/.editorconfig index 140cc085c5..231d35cfe4 100644 --- a/.editorconfig +++ b/.editorconfig @@ -1,901 +1,901 @@ -[*] -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 +[*] +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 = false +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 = true +ij_kotlin_method_parameters_right_paren_on_new_line = true +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/bug.yml b/.github/ISSUE_TEMPLATE/bug.yml index a7daaac14b..1ab5d835b2 100644 --- a/.github/ISSUE_TEMPLATE/bug.yml +++ b/.github/ISSUE_TEMPLATE/bug.yml @@ -73,3 +73,14 @@ body: - 'No' validations: required: true + - type: dropdown + id: pr + attributes: + label: Are you willing to provide a PR? + description: | + Providing a PR can drastically speed up the process of fixing this bug. Don't worry, it's still OK to answer 'No' :). + options: + - 'Yes' + - 'No' + validations: + required: true diff --git a/.github/ISSUE_TEMPLATE/enhancement.yml b/.github/ISSUE_TEMPLATE/enhancement.yml index 71adce718e..2dd968951f 100644 --- a/.github/ISSUE_TEMPLATE/enhancement.yml +++ b/.github/ISSUE_TEMPLATE/enhancement.yml @@ -34,3 +34,14 @@ body: placeholder: Is there anything else you'd like to add? validations: required: false + - type: dropdown + id: pr + attributes: + label: Are you willing to provide a PR? + description: | + Don't worry, it's still OK to answer 'No' :). + options: + - 'Yes' + - 'No' + validations: + required: true diff --git a/.github/ISSUE_TEMPLATE/release.yml b/.github/ISSUE_TEMPLATE/release.yml index 7cb47fa952..f012900752 100644 --- a/.github/ISSUE_TEMPLATE/release.yml +++ b/.github/ISSUE_TEMPLATE/release.yml @@ -49,24 +49,34 @@ body: ### Once tested and validated internally - - [ ] Create a new beta release on the GooglePlay console and upload the 4 signed Apks. + - [ ] Create a new open testing release on the GooglePlay console and upload the 4 signed Apks. - [ ] Check that the version codes are correct - [ ] Copy the fastlane change to the GooglePlay console in the section en-GB. - - [ ] Push to beta release to 100% of the users - - [ ] Notify the F-Droid team so that they can schedule the publication on F-Droid + - [ ] Push the open testing release to 100% of the users + - [ ] Notify the F-Droid team [here](https://matrix.to/#/!LAAuJLQXYHjMNWKrCK:matrix.org?via=matrix.org&via=bubu1.eu&via=lant.uk) so that they can schedule the publication on F-Droid + - [ ] The application is available to the PlayStore testers (live). Google can take between 1 hour and up to 7 days to approve the release. + - [ ] The application is available to the F-Droid users. - ### Once Live on PlayStore + ### Once open testing is live on PlayStore - [ ] Ping the Android public room and update its topic - - [ ] Add an entry in the internal diary - ### After at least 2 days + ### Once Live on F-Droid + + - [ ] Update the Android public room topic + + ### After at least 2 days (generally next Monday) - [ ] Check the [rageshakes](https://github.com/matrix-org/element-android-rageshakes/issues) - [ ] Check the crash reports on the GooglePlay console - [ ] Check the Android Element room for any reported issues on the new version - - [ ] If all is OK, push to production and notify Markus (Bubu) to release the F-Droid version - - [ ] Ping the Android public room and update its topic with the new available version + - [ ] If all is OK, promote the open testing release to production. Generally using a 100% roll out, but can be a smaller value depending on the release content. + - [ ] The application is available to the PlayStore users (live). Google can take (again!) between 1 hour and up to 7 days to approve the release. + + ### Once production is live on PlayStore + + - [ ] Ping the Android public room and update its topic + - [ ] Add an entry in the internal diary ### Android SDK2 diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 9517a4f3a7..1c0491fda4 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -8,8 +8,9 @@ on: # Enrich gradle.properties for CI/CD env: CI_GRADLE_ARG_PROPERTIES: > - -Porg.gradle.jvmargs=-Xmx2g + -Porg.gradle.jvmargs=-Xmx4g -Porg.gradle.parallel=false + --no-daemon jobs: debug: @@ -46,8 +47,9 @@ jobs: release: name: Build unsigned GPlay APKs runs-on: ubuntu-latest - if: github.ref == 'refs/heads/main' - # Only runs on main, no concurrency. + concurrency: + group: ${{ github.ref == 'refs/head/main' && format('build-release-apk-main-{0}', github.sha) || github.ref == 'refs/heads/develop' && format('build-release-apk-develop-{0}', github.sha) || format('build-debug-{0}', github.ref) }} + cancel-in-progress: ${{ github.ref != 'refs/head/main' }} steps: - uses: actions/checkout@v3 - uses: actions/cache@v3 @@ -59,7 +61,7 @@ jobs: restore-keys: | ${{ runner.os }}-gradle- - name: Assemble GPlay unsigned apk - run: ./gradlew clean assembleGplayRelease $CI_GRADLE_ARG_PROPERTIES --stacktrace + run: ./gradlew clean assembleGplayRelease $CI_GRADLE_ARG_PROPERTIES --stacktrace - name: Upload Gplay unsigned APKs uses: actions/upload-artifact@v3 with: @@ -67,4 +69,26 @@ jobs: path: | vector/build/outputs/apk/*/release/*.apk -# TODO add exodus checks + exodus: + runs-on: ubuntu-latest + needs: release + steps: + - name: Obtain apk from artifact + id: download + uses: actions/download-artifact@v3 + with: + name: vector-gplay-release-unsigned + - name: Show apks in artifact + run: ls -R ${{steps.download.outputs.download-path}} + - name: Execute exodus-standalone + uses: docker://exodusprivacy/exodus-standalone:latest + with: + args: /github/workspace/gplay/release/vector-gplay-universal-release-unsigned.apk -j -o /github/workspace/exodus.json + - name: Upload exodus json report + uses: actions/upload-artifact@v3 + with: + name: exodus.json + path: | + exodus.json + - name: Check for trackers + run: "jq -e '.trackers == []' exodus.json > /dev/null || { echo '::error static analysis identified user tracking library' ; exit 1; }" diff --git a/.github/workflows/post-pr.yml b/.github/workflows/post-pr.yml index 8fe51eb8d5..a7f1d6f204 100644 --- a/.github/workflows/post-pr.yml +++ b/.github/workflows/post-pr.yml @@ -13,6 +13,7 @@ env: CI_GRADLE_ARG_PROPERTIES: > -Porg.gradle.jvmargs=-Xmx4g -Porg.gradle.parallel=false + --no-daemon jobs: @@ -29,200 +30,6 @@ jobs: steps: - run: echo "Run those tests!" # no-op success - # Run Android Tests - integration-tests: - name: Matrix SDK - Running Integration Tests - needs: should-i-run - runs-on: macos-latest - strategy: - fail-fast: false - matrix: - api-level: [ 28 ] - steps: - - uses: actions/checkout@v3 - - uses: gradle/wrapper-validation-action@v1 - - uses: actions/setup-java@v3 - with: - distribution: 'adopt' - java-version: 11 - - name: Set up Python 3.8 - uses: actions/setup-python@v3 - with: - python-version: 3.8 - - uses: actions/cache@v3 - with: - path: | - ~/.gradle/caches - ~/.gradle/wrapper - key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle*', '**/gradle-wrapper.properties') }} - restore-keys: | - ${{ runner.os }}-gradle- - - name: Start synapse server - uses: michaelkaye/setup-matrix-synapse@v1.0.3 - with: - uploadLogs: true - httpPort: 8080 - disableRateLimiting: true - public_baseurl: "http://10.0.2.2:8080/" - # package: org.matrix.android.sdk.session - - name: Run integration tests for Matrix SDK [org.matrix.android.sdk.session] API[${{ matrix.api-level }}] - uses: reactivecircus/android-emulator-runner@v2 - with: - api-level: ${{ matrix.api-level }} - arch: x86 - profile: Nexus 5X - force-avd-creation: false - emulator-options: -no-snapshot-save -no-window -gpu swiftshader_indirect -noaudio -no-boot-anim -camera-back none - emulator-build: 7425822 - script: | - adb root - adb logcat -c - touch emulator-session.log - chmod 777 emulator-session.log - adb logcat >> emulator-session.log & - ./gradlew $CI_GRADLE_ARG_PROPERTIES -Pandroid.testInstrumentationRunnerArguments.package='org.matrix.android.sdk.session' matrix-sdk-android:connectedDebugAndroidTest - - name: Read Results [org.matrix.android.sdk.session] - if: always() - id: get-comment-body-session - run: python3 ./tools/ci/render_test_output.py session ./matrix-sdk-android/build/outputs/androidTest-results/connected/*.xml - - name: Remove adb logcat - if: always() - run: pkill -9 adb - - name: Run integration tests for Matrix SDK [org.matrix.android.sdk.account] API[${{ matrix.api-level }}] - if: always() - uses: reactivecircus/android-emulator-runner@v2 - with: - api-level: ${{ matrix.api-level }} - arch: x86 - profile: Nexus 5X - force-avd-creation: false - emulator-options: -no-snapshot-save -no-window -gpu swiftshader_indirect -noaudio -no-boot-anim -camera-back none - emulator-build: 7425822 - script: | - adb root - adb logcat -c - touch emulator-account.log - chmod 777 emulator-account.log - adb logcat >> emulator-account.log & - ./gradlew $CI_GRADLE_ARG_PROPERTIES -Pandroid.testInstrumentationRunnerArguments.package='org.matrix.android.sdk.account' matrix-sdk-android:connectedDebugAndroidTest - - name: Read Results [org.matrix.android.sdk.account] - if: always() - id: get-comment-body-account - run: python3 ./tools/ci/render_test_output.py account ./matrix-sdk-android/build/outputs/androidTest-results/connected/*.xml - - name: Remove adb logcat - if: always() - run: pkill -9 adb - # package: org.matrix.android.sdk.internal - - name: Run integration tests for Matrix SDK [org.matrix.android.sdk.internal] API[${{ matrix.api-level }}] - if: always() - uses: reactivecircus/android-emulator-runner@v2 - with: - api-level: ${{ matrix.api-level }} - arch: x86 - profile: Nexus 5X - force-avd-creation: false - emulator-options: -no-snapshot-save -no-window -gpu swiftshader_indirect -noaudio -no-boot-anim -camera-back none - emulator-build: 7425822 - script: | - adb root - adb logcat -c - touch emulator-internal.log - chmod 777 emulator-internal.log - adb logcat >> emulator-internal.log & - ./gradlew $CI_GRADLE_ARG_PROPERTIES -Pandroid.testInstrumentationRunnerArguments.package='org.matrix.android.sdk.internal' matrix-sdk-android:connectedDebugAndroidTest - - name: Read Results [org.matrix.android.sdk.internal] - if: always() - id: get-comment-body-internal - run: python3 ./tools/ci/render_test_output.py internal ./matrix-sdk-android/build/outputs/androidTest-results/connected/*.xml - - name: Remove adb logcat - if: always() - run: pkill -9 adb - # package: org.matrix.android.sdk.ordering - - name: Run integration tests for Matrix SDK [org.matrix.android.sdk.ordering] API[${{ matrix.api-level }}] - if: always() - uses: reactivecircus/android-emulator-runner@v2 - with: - api-level: ${{ matrix.api-level }} - arch: x86 - profile: Nexus 5X - force-avd-creation: false - emulator-options: -no-snapshot-save -no-window -gpu swiftshader_indirect -noaudio -no-boot-anim -camera-back none - emulator-build: 7425822 - script: | - adb root - adb logcat -c - touch emulator-ordering.log - chmod 777 emulator-ordering.log - adb logcat >> emulator-ordering.log & - ./gradlew $CI_GRADLE_ARG_PROPERTIES -Pandroid.testInstrumentationRunnerArguments.package='org.matrix.android.sdk.ordering' matrix-sdk-android:connectedDebugAndroidTest - - name: Read Results [org.matrix.android.sdk.ordering] - if: always() - id: get-comment-body-ordering - run: python3 ./tools/ci/render_test_output.py ordering ./matrix-sdk-android/build/outputs/androidTest-results/connected/*.xml - - name: Remove adb logcat - if: always() - run: pkill -9 adb - # package: class PermalinkParserTest - - name: Run integration tests for Matrix SDK class [org.matrix.android.sdk.PermalinkParserTest] API[${{ matrix.api-level }}] - if: always() - uses: reactivecircus/android-emulator-runner@v2 - with: - api-level: ${{ matrix.api-level }} - arch: x86 - profile: Nexus 5X - force-avd-creation: false - emulator-options: -no-snapshot-save -no-window -gpu swiftshader_indirect -noaudio -no-boot-anim -camera-back none - emulator-build: 7425822 - script: | - adb root - adb logcat -c - touch emulator-permalink.log - chmod 777 emulator-permalink.log - adb logcat >> emulator-permalink.log & - ./gradlew $CI_GRADLE_ARG_PROPERTIES -Pandroid.testInstrumentationRunnerArguments.class='org.matrix.android.sdk.PermalinkParserTest' matrix-sdk-android:connectedDebugAndroidTest - - name: Read Results [org.matrix.android.sdk.PermalinkParserTest] - if: always() - id: get-comment-body-permalink - run: python3 ./tools/ci/render_test_output.py permalink ./matrix-sdk-android/build/outputs/androidTest-results/connected/*.xml - - name: Remove adb logcat - if: always() - run: pkill -9 adb - # package: class PermalinkParserTest - - name: Find Comment - if: always() && github.event_name == 'pull_request' - uses: peter-evans/find-comment@v2 - id: fc - with: - issue-number: ${{ github.event.pull_request.number }} - comment-author: 'github-actions[bot]' - body-includes: Integration Tests Results - - name: Publish results to PR - if: always() && github.event_name == 'pull_request' - uses: peter-evans/create-or-update-comment@v2 - with: - comment-id: ${{ steps.fc.outputs.comment-id }} - issue-number: ${{ github.event.pull_request.number }} - body: | - ### Matrix SDK - ## Integration Tests Results: - - `[org.matrix.android.sdk.session]`
${{ steps.get-comment-body-session.outputs.session }} - - `[org.matrix.android.sdk.account]`
${{ steps.get-comment-body-account.outputs.account }} - - `[org.matrix.android.sdk.internal]`
${{ steps.get-comment-body-internal.outputs.internal }} - - `[org.matrix.android.sdk.ordering]`
${{ steps.get-comment-body-ordering.outputs.ordering }} - - `[org.matrix.android.sdk.PermalinkParserTest]`
${{ steps.get-comment-body-permalink.outputs.permalink }} - edit-mode: replace - - name: Upload Test Report Log - uses: actions/upload-artifact@v3 - if: always() - with: - name: integrationtest-error-results - path: | - emulator-permalink.log - emulator-internal.log - emulator-ordering.log - emulator-account.log - emulator-session.log - ui-tests: name: UI Tests (Synapse) needs: should-i-run @@ -235,7 +42,7 @@ jobs: steps: - uses: actions/checkout@v3 - name: Set up Python 3.8 - uses: actions/setup-python@v3 + uses: actions/setup-python@v4 with: python-version: 3.8 - uses: actions/cache@v3 @@ -282,42 +89,13 @@ jobs: emulator.log failure_screenshots/ - codecov-units: - name: Unit tests with code coverage - needs: should-i-run - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v3 - - uses: actions/setup-java@v3 - with: - distribution: 'adopt' - java-version: '11' - - uses: actions/cache@v3 - with: - path: | - ~/.gradle/caches - ~/.gradle/wrapper - key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle*', '**/gradle-wrapper.properties') }} - restore-keys: | - ${{ runner.os }}-gradle- - - run: ./gradlew allCodeCoverageReport $CI_GRADLE_ARG_PROPERTIES - - name: Upload Codecov data - uses: actions/upload-artifact@v3 - if: always() - with: - name: codecov-xml - path: | - build/reports/jacoco/allCodeCoverageReport/allCodeCoverageReport.xml - # Notify the channel about delayed failures notify: name: Notify matrix runs-on: ubuntu-latest needs: - should-i-run - - integration-tests - ui-tests - - codecov-units if: always() && (needs.should-i-run.result == 'success' ) && ((needs.codecov-units.result != 'success' ) || (needs.ui-tests.result != 'success') || (needs.integration-tests.result != 'success')) # No concurrency required, runs every time on a schedule. steps: diff --git a/.github/workflows/quality.yml b/.github/workflows/quality.yml index a97d532644..d7f5ce8b57 100644 --- a/.github/workflows/quality.yml +++ b/.github/workflows/quality.yml @@ -5,6 +5,13 @@ on: push: branches: [ main, develop ] +# Enrich gradle.properties for CI/CD +env: + CI_GRADLE_ARG_PROPERTIES: > + -Porg.gradle.jvmargs=-Xmx4g + -Porg.gradle.parallel=false + --no-daemon + jobs: check: name: Project Check Suite @@ -97,6 +104,25 @@ jobs: comment_id: ${{ steps.fc.outputs.comment-id }} }) +# Gradle dependency analysis using https://github.com/autonomousapps/dependency-analysis-android-gradle-plugin + dependency-analysis: + name: Dependency analysis + runs-on: ubuntu-latest + # Allow all jobs on main and develop. Just one per PR. + concurrency: + group: ${{ github.ref == 'refs/heads/main' && format('dep-main-{0}', github.sha) || github.ref == 'refs/heads/develop' && format('dep-develop-{0}', github.sha) || format('dep-{0}', github.ref) }} + cancel-in-progress: true + steps: + - uses: actions/checkout@v3 + - name: Dependency analysis + run: ./gradlew dependencyCheckAnalyze $CI_GRADLE_ARG_PROPERTIES + - name: Upload dependency analysis + if: always() + uses: actions/upload-artifact@v3 + with: + name: dependency-analysis + path: build/reports/dependency-check-report.html + # Lint for main module android-lint: name: Android Linter @@ -116,7 +142,7 @@ jobs: restore-keys: | ${{ runner.os }}-gradle- - name: Lint analysis - run: ./gradlew clean :vector:lint --stacktrace + run: ./gradlew clean :vector:lint --stacktrace $CI_GRADLE_ARG_PROPERTIES - name: Upload reports if: always() uses: actions/upload-artifact@v3 @@ -149,7 +175,7 @@ jobs: restore-keys: | ${{ runner.os }}-gradle- - name: Lint ${{ matrix.target }} release - run: ./gradlew clean lint${{ matrix.target }}Release --stacktrace + run: ./gradlew clean lint${{ matrix.target }}Release --stacktrace $CI_GRADLE_ARG_PROPERTIES - name: Upload ${{ matrix.target }} linting report if: always() uses: actions/upload-artifact@v3 @@ -169,7 +195,7 @@ jobs: - uses: actions/checkout@v3 - name: Run detekt run: | - ./gradlew detekt + ./gradlew detekt $CI_GRADLE_ARG_PROPERTIES - name: Upload reports if: always() uses: actions/upload-artifact@v3 diff --git a/.github/workflows/sonarqube.yml b/.github/workflows/sonarqube.yml deleted file mode 100644 index 6809751d91..0000000000 --- a/.github/workflows/sonarqube.yml +++ /dev/null @@ -1,81 +0,0 @@ -name: Sonarqube nightly - -on: - schedule: - - cron: '0 20 * * *' - -# Enrich gradle.properties for CI/CD -env: - CI_GRADLE_ARG_PROPERTIES: > - -Porg.gradle.jvmargs=-Xmx4g - -Porg.gradle.parallel=false -jobs: - codecov-units: - name: Unit tests with code coverage - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v3 - - uses: actions/setup-java@v3 - with: - distribution: 'adopt' - java-version: '11' - - uses: actions/cache@v3 - with: - path: | - ~/.gradle/caches - ~/.gradle/wrapper - key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle*', '**/gradle-wrapper.properties') }} - restore-keys: | - ${{ runner.os }}-gradle- - - run: ./gradlew allCodeCoverageReport $CI_GRADLE_ARG_PROPERTIES - - name: Upload Codecov data - uses: actions/upload-artifact@v3 - if: always() - with: - name: codecov-xml - path: | - build/reports/jacoco/allCodeCoverageReport/allCodeCoverageReport.xml - - sonarqube: - name: Sonarqube upload - runs-on: ubuntu-latest - needs: - - codecov-units - steps: - - uses: actions/checkout@v3 - - uses: actions/setup-java@v3 - with: - distribution: 'adopt' - java-version: '11' - - uses: actions/cache@v3 - with: - path: | - ~/.gradle/caches - ~/.gradle/wrapper - key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle*', '**/gradle-wrapper.properties') }} - restore-keys: | - ${{ runner.os }}-gradle- - - uses: actions/download-artifact@v3 - with: - name: codecov-xml # will restore to allCodeCoverageReport.xml by default; we restore to the same location in following tasks - - run: mkdir -p build/reports/jacoco/allCodeCoverageReport/ - - run: mv allCodeCoverageReport.xml build/reports/jacoco/allCodeCoverageReport/ - - run: ./gradlew sonarqube $CI_GRADLE_ARG_PROPERTIES - env: - ORG_GRADLE_PROJECT_SONAR_LOGIN: ${{ secrets.SONAR_TOKEN }} - -# Notify the channel about sonarqube failures - notify: - name: Notify matrix - runs-on: ubuntu-latest - needs: - - sonarqube - - codecov-units - if: always() && (needs.sonarqube.result != 'success' || needs.codecov-units.result != 'success') - steps: - - uses: michaelkaye/matrix-hookshot-action@v1.0.0 - with: - github_token: ${{ secrets.GITHUB_TOKEN }} - hookshot_url: ${{ secrets.ELEMENT_ANDROID_HOOKSHOT_URL }} - text_template: "Sonarqube run (on ${{ github.ref }}): {{#each job_statuses }}{{#with this }}{{#if completed }} {{name}} {{conclusion}} at {{completed_at}}, {{/if}}{{/with}}{{/each}}" - html_template: "Sonarqube run (on ${{ github.ref }}): {{#each job_statuses }}{{#with this }}{{#if completed }}
{{icon conclusion}} {{name}} {{conclusion}} at {{completed_at}} [details]{{/if}}{{/with}}{{/each}}" diff --git a/.github/workflows/sync-from-external-sources.yml b/.github/workflows/sync-from-external-sources.yml index 796d915ea6..fdc1bbcb96 100644 --- a/.github/workflows/sync-from-external-sources.yml +++ b/.github/workflows/sync-from-external-sources.yml @@ -13,7 +13,7 @@ jobs: steps: - uses: actions/checkout@v3 - name: Set up Python 3.8 - uses: actions/setup-python@v3 + uses: actions/setup-python@v4 with: python-version: 3.8 - name: Install Prerequisite dependencies @@ -40,7 +40,7 @@ jobs: steps: - uses: actions/checkout@v3 - name: Set up Python 3.8 - uses: actions/setup-python@v3 + uses: actions/setup-python@v4 with: python-version: 3.8 - name: Install Prerequisite dependencies diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 3e8de8979c..1a9cc5c239 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -8,77 +8,111 @@ on: # Enrich gradle.properties for CI/CD env: CI_GRADLE_ARG_PROPERTIES: > - -Porg.gradle.jvmargs=-Xmx2g + -Porg.gradle.jvmargs=-Xmx4g -Porg.gradle.parallel=false + --no-daemon jobs: - # Build Android Tests - build-android-tests: - name: Build Android Tests - runs-on: ubuntu-latest - concurrency: - group: ${{ github.ref == 'refs/heads/main' && format('unit-tests-main-{0}', github.sha) || github.ref == 'refs/heads/develop' && format('unit-tests-develop-{0}', github.sha) || format('build-android-tests-{0}', github.ref) }} - cancel-in-progress: true - steps: - - uses: actions/checkout@v3 - - uses: actions/setup-java@v3 - with: - distribution: 'adopt' - java-version: 11 - - uses: actions/cache@v3 - with: - path: | - ~/.gradle/caches - ~/.gradle/wrapper - key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle*', '**/gradle-wrapper.properties') }} - restore-keys: | - ${{ runner.os }}-gradle- - - name: Build Android Tests - run: ./gradlew clean assembleAndroidTest $CI_GRADLE_ARG_PROPERTIES --stacktrace - - unit-tests: - name: Run Unit Tests - runs-on: ubuntu-latest + tests: + name: Runs all tests + runs-on: macos-latest # for the emulator # Allow all jobs on main and develop. Just one per PR. concurrency: group: ${{ github.ref == 'refs/heads/main' && format('unit-tests-main-{0}', github.sha) || github.ref == 'refs/heads/develop' && format('unit-tests-develop-{0}', github.sha) || format('unit-tests-{0}', github.ref) }} cancel-in-progress: true steps: - uses: actions/checkout@v3 - - uses: actions/cache@v3 with: - path: | - ~/.gradle/caches - ~/.gradle/wrapper - key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle*', '**/gradle-wrapper.properties') }} - restore-keys: | - ${{ runner.os }}-gradle- - - name: Run unit tests - run: ./gradlew clean test $CI_GRADLE_ARG_PROPERTIES --stacktrace + fetch-depth: 0 + - uses: actions/setup-java@v3 + with: + distribution: 'adopt' + java-version: '11' + - uses: gradle/gradle-build-action@v2 + - uses: actions/setup-python@v4 + with: + python-version: 3.8 + - uses: michaelkaye/setup-matrix-synapse@v1.0.3 + with: + uploadLogs: true + httpPort: 8080 + disableRateLimiting: true + public_baseurl: "http://10.0.2.2:8080/" + - name: Run all the codecoverage tests at once + id: tests + uses: reactivecircus/android-emulator-runner@v2 + continue-on-error: true + with: + api-level: 28 + arch: x86 + profile: Nexus 5X + force-avd-creation: false + emulator-options: -no-snapshot-save -no-window -gpu swiftshader_indirect -noaudio -no-boot-anim -camera-back none + disable-animations: true + emulator-build: 7425822 + script: | + ./gradlew unitTestsWithCoverage --stacktrace $CI_GRADLE_ARG_PROPERTIES + ./gradlew instrumentationTestsWithCoverage --stacktrace $CI_GRADLE_ARG_PROPERTIES + ./gradlew generateCoverageReport --stacktrace $CI_GRADLE_ARG_PROPERTIES +# NB: continue-on-error marks steps.tests.conclusion = 'success' but leaves stes.tests.outcome = 'failure' + - name: Run all the codecoverage tests at once (retry if emulator failed) + uses: reactivecircus/android-emulator-runner@v2 + if: always() && steps.tests.outcome == 'failure' # don't run if previous step succeeded. + with: + api-level: 28 + arch: x86 + profile: Nexus 5X + force-avd-creation: false + emulator-options: -no-snapshot-save -no-window -gpu swiftshader_indirect -noaudio -no-boot-anim -camera-back none + disable-animations: true + emulator-build: 7425822 + script: | + ./gradlew unitTestsWithCoverage --stacktrace $CI_GRADLE_ARG_PROPERTIES + ./gradlew instrumentationTestsWithCoverage --stacktrace $CI_GRADLE_ARG_PROPERTIES + ./gradlew generateCoverageReport --stacktrace $CI_GRADLE_ARG_PROPERTIES + - run: ./gradlew sonarqube $CI_GRADLE_ARG_PROPERTIES + if: always() # we may have failed a previous step and retried, that's OK + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} # Needed to get PR information, if any + SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }} + ORG_GRADLE_PROJECT_SONAR_LOGIN: ${{ secrets.SONAR_TOKEN }} + - name: Format unit test results if: always() run: python3 ./tools/ci/render_test_output.py unit ./**/build/test-results/**/*.xml - - name: Publish Unit Test Results - uses: EnricoMi/publish-unit-test-result-action@v1 - if: always() && - github.event.sender.login != 'dependabot[bot]' && - ( github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.repository ) - with: - files: ./**/build/test-results/**/*.xml -# Notify the channel about runs against develop or main that have failures, as PRs should have caught these first. - notify: - runs-on: ubuntu-latest - needs: - - unit-tests - - build-android-tests - if: ${{ (github.ref == 'refs/heads/develop' || github.ref == 'refs/heads/main' ) && failure() }} - steps: - - uses: michaelkaye/matrix-hookshot-action@v0.3.0 - with: - github_token: ${{ secrets.GITHUB_TOKEN }} - matrix_access_token: ${{ secrets.ELEMENT_ANDROID_NOTIFICATION_ACCESS_TOKEN }} - matrix_room_id: ${{ secrets.ELEMENT_ANDROID_INTERNAL_ROOM_ID }} - text_template: "Build is broken for ${{ github.ref }}: {{#each job_statuses }}{{#with this }}{{#if completed }}{{name}} {{conclusion}} at {{completed_at}}, {{/if}}{{/with}}{{/each}}" - html_template: "Build is broken for ${{ github.ref }}: {{#each job_statuses }}{{#with this }}{{#if completed }}
{{icon conclusion }} {{name}} {{conclusion}} at {{completed_at}} [details]{{/if}}{{/with}}{{/each}}" +# can't be run on macos due to containers. +# - name: Publish Unit Test Results +# uses: EnricoMi/publish-unit-test-result-action@v1 +# if: always() && +# github.event.sender.login != 'dependabot[bot]' && +# ( github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.repository ) +# with: +# files: ./**/build/test-results/**/*.xml + +# Unneeded as part of the test suite above, kept around in case we want to re-enable them. +# +# # Build Android Tests +# build-android-tests: +# name: Build Android Tests +# runs-on: ubuntu-latest +# concurrency: +# group: ${{ github.ref == 'refs/heads/main' && format('unit-tests-main-{0}', github.sha) || github.ref == 'refs/heads/develop' && format('unit-tests-develop-{0}', github.sha) || format('build-android-tests-{0}', github.ref) }} +# cancel-in-progress: true +# steps: +# - uses: actions/checkout@v3 +# - uses: actions/setup-java@v3 +# with: +# distribution: 'adopt' +# java-version: 11 +# - uses: actions/cache@v3 +# with: +# path: | +# ~/.gradle/caches +# ~/.gradle/wrapper +# key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle*', '**/gradle-wrapper.properties') }} +# restore-keys: | +# ${{ runner.os }}-gradle- +# - name: Build Android Tests +# run: ./gradlew clean assembleAndroidTest $CI_GRADLE_ARG_PROPERTIES --stacktrace diff --git a/.github/workflows/triage-labelled.yml b/.github/workflows/triage-labelled.yml index 82d5931ce7..c49b33d582 100644 --- a/.github/workflows/triage-labelled.yml +++ b/.github/workflows/triage-labelled.yml @@ -9,15 +9,15 @@ jobs: name: Add Z-Labs label for features behind labs flags runs-on: ubuntu-latest 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-Polls') || - contains(github.event.issue.labels.*.name, 'A-Location-Sharing') || - contains(github.event.issue.labels.*.name, 'A-Message-Bubbles') || - contains(github.event.issue.labels.*.name, 'Z-IA') || - contains(github.event.issue.labels.*.name, 'A-Themes-Custom') || - contains(github.event.issue.labels.*.name, 'A-E2EE-Dehydration') || - contains(github.event.issue.labels.*.name, 'A-Tags') + contains(github.event.issue.labels.*.name, 'A-Maths') || + contains(github.event.issue.labels.*.name, 'A-Message-Pinning') || + 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') || + contains(github.event.issue.labels.*.name, 'Z-IA') || + contains(github.event.issue.labels.*.name, 'A-Themes-Custom') || + contains(github.event.issue.labels.*.name, 'A-E2EE-Dehydration') || + contains(github.event.issue.labels.*.name, 'A-Tags') steps: - uses: actions/github-script@v5 with: @@ -79,7 +79,7 @@ jobs: name: X-Needs-Product to Design project board runs-on: ubuntu-latest if: > - contains(github.event.issue.labels.*.name, 'X-Needs-Product') + contains(github.event.issue.labels.*.name, 'X-Needs-Product') steps: - uses: octokit/graphql-action@v2.x id: add_to_project @@ -105,10 +105,7 @@ jobs: # Skip in forks if: > github.repository == 'vector-im/element-android' && - (contains(github.event.issue.labels.*.name, 'A-Spaces') || - contains(github.event.issue.labels.*.name, 'A-Space-Settings') || - contains(github.event.issue.labels.*.name, 'A-Subspaces') || - contains(github.event.issue.labels.*.name, 'Z-IA')) + (contains(github.event.issue.labels.*.name, 'Team: Delight')) steps: - uses: octokit/graphql-action@v2.x with: @@ -201,7 +198,7 @@ jobs: env: PROJECT_ID: "PN_kwDOAM0swc3m-g" GITHUB_TOKEN: ${{ secrets.ELEMENT_BOT_TOKEN }} - + move_ftue_issues: name: Z-FTUE to Mobile FTUE board runs-on: ubuntu-latest diff --git a/.github/workflows/triage-priority-bugs.yml b/.github/workflows/triage-priority-bugs.yml index 70c337e748..6cde154370 100644 --- a/.github/workflows/triage-priority-bugs.yml +++ b/.github/workflows/triage-priority-bugs.yml @@ -14,10 +14,7 @@ jobs: !contains(github.event.issue.labels.*.name, 'A-E2EE-Cross-Signing') && !contains(github.event.issue.labels.*.name, 'A-E2EE-Dehydration') && !contains(github.event.issue.labels.*.name, 'A-E2EE-Key-Backup') && - !contains(github.event.issue.labels.*.name, 'A-E2EE-SAS-Verification') && - !contains(github.event.issue.labels.*.name, 'A-Spaces') && - !contains(github.event.issue.labels.*.name, 'A-Spaces-Settings') && - !contains(github.event.issue.labels.*.name, 'A-Subspaces')) && + !contains(github.event.issue.labels.*.name, 'A-E2EE-SAS-Verification')) && (contains(github.event.issue.labels.*.name, 'T-Defect') && contains(github.event.issue.labels.*.name, 'S-Critical') && (contains(github.event.issue.labels.*.name, 'O-Frequent') || diff --git a/.gitignore b/.gitignore index ff086d7723..8313fb5c63 100644 --- a/.gitignore +++ b/.gitignore @@ -16,4 +16,4 @@ /fastlane/private /fastlane/report.xml -/library/build +/**/build diff --git a/.idea/dictionaries/bmarty.xml b/.idea/dictionaries/bmarty.xml index 85290e72df..c29bca95f2 100644 --- a/.idea/dictionaries/bmarty.xml +++ b/.idea/dictionaries/bmarty.xml @@ -40,6 +40,7 @@ sygnal threepid uisi + unifiedpush unpublish unwedging vctr diff --git a/CHANGES.md b/CHANGES.md index 4ff45e9c62..17d3fed2a6 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,3 +1,146 @@ +Changes in Element v1.4.25 (2022-06-27) +======================================= + +Bugfixes 🐛 +---------- +- Second attempt to fix session database migration to version 30. + +Changes in Element v1.4.24 (2022-06-22) +======================================= + +Bugfixes 🐛 +---------- +- First attempt to fix session database migration to version 30. + +Changes in Element v1.4.23 (2022-06-21) +======================================= + +Bugfixes 🐛 +---------- + - Fix loop in timeline and simplify management of chunks and timeline events. ([#6318](https://github.com/vector-im/element-android/issues/6318)) + + +Changes in Element v1.4.22 (2022-06-14) +======================================= + +Features ✨ +---------- + - Make read receipt avatar list more compact ([#5970](https://github.com/vector-im/element-android/issues/5970)) + - Allow .well-known configuration to override key sharing mode ([#6147](https://github.com/vector-im/element-android/issues/6147)) + - Re-organize location settings flags ([#6244](https://github.com/vector-im/element-android/issues/6244)) + - Add report action for live location messages ([#6280](https://github.com/vector-im/element-android/issues/6280)) + +Bugfixes 🐛 +---------- + - Fix cases of missing, swapped, or duplicated messages ([#5528](https://github.com/vector-im/element-android/issues/5528)) + - Fix wrong status of live location sharing in timeline ([#6209](https://github.com/vector-im/element-android/issues/6209)) + - Fix StackOverflowError while recording voice message ([#6222](https://github.com/vector-im/element-android/issues/6222)) + - Text cropped: "Secure backup" ([#6232](https://github.com/vector-im/element-android/issues/6232)) + - Fix copyright attributions of map views ([#6247](https://github.com/vector-im/element-android/issues/6247)) + - Fix flickering bottom bar of live location item ([#6264](https://github.com/vector-im/element-android/issues/6264)) + +In development 🚧 +---------------- + - FTUE - Adds Sign Up tracking ([#5285](https://github.com/vector-im/element-android/issues/5285)) + +SDK API changes ⚠️ +------------------ + - Some methods from `Session` have been moved to a new `SyncService`, that you can retrieve from a `Session`. + - `SyncStatusService` method has been moved to the new `SyncService` + - `InitSyncStep` have been moved and renamed to `InitialSyncStep` + - `SyncStatusService.Status` has been renamed to `SyncRequestState` + - The existing `SyncService` has been renamed to `SyncAndroidService` because of name clash with the new SDK Service ([#6029](https://github.com/vector-im/element-android/issues/6029)) + - Allows `AuthenticationService.getLoginFlow` to fail without resetting state from previously successful calls ([#6093](https://github.com/vector-im/element-android/issues/6093)) + - Allows new passwords to be passed at the point of confirmation when resetting a password ([#6169](https://github.com/vector-im/element-android/issues/6169)) + +Other changes +------------- + - Adds support for parsing homeserver versions without a patch number ([#6017](https://github.com/vector-im/element-android/issues/6017)) + - Updating exit onboarding dialog copy formatting to match iOS ([#6087](https://github.com/vector-im/element-android/issues/6087)) + - Disables when arrow alignment in code style ([#6126](https://github.com/vector-im/element-android/issues/6126)) + + +Changes in Element 1.4.20 (2022-06-13) +====================================== + +Bugfixes 🐛 +---------- + - Fix: All rooms are shown in Home regardless of the switch state. ([#6272](https://github.com/vector-im/element-android/issues/6272)) + - Fix regression on EventInsertLiveObserver getting blocked so there is no event being processed anymore. ([#6278](https://github.com/vector-im/element-android/issues/6278)) + + +Changes in Element 1.4.19 (2022-06-07) +====================================== + +Bugfixes 🐛 +---------- + - Fix | performance regression on roomlist + proper display of space parents in explore rooms. ([#6233](https://github.com/vector-im/element-android/issues/6233)) + + +Changes in Element v1.4.18 (2022-05-31) +======================================= + +Features ✨ +---------- + - Space explore screen changes: removed space card, added rooms filtering ([#5658](https://github.com/vector-im/element-android/issues/5658)) + - Adds space or user id as a subtitle under rooms in search ([#5860](https://github.com/vector-im/element-android/issues/5860)) + - Adds up navigation in spaces ([#6073](https://github.com/vector-im/element-android/issues/6073)) + - Labs flag for enabling live location sharing ([#6098](https://github.com/vector-im/element-android/issues/6098)) + - Added support for mandatory backup or passphrase from .well-known configuration. ([#6133](https://github.com/vector-im/element-android/issues/6133)) + - Security - Asking for user confirmation when tapping URLs which contain unicode directional overrides ([#6163](https://github.com/vector-im/element-android/issues/6163)) + - Add settings switch to allow autoplaying animated images ([#6166](https://github.com/vector-im/element-android/issues/6166)) + - Live Location Sharing - User List Bottom Sheet ([#6170](https://github.com/vector-im/element-android/issues/6170)) + +Bugfixes 🐛 +---------- + - Fix some notifications not clearing when read ([#4862](https://github.com/vector-im/element-android/issues/4862)) + - Do not switch away from home space on notification when "Show all Rooms in Home" is selected. ([#5827](https://github.com/vector-im/element-android/issues/5827)) + - Use fixed text size in read receipt counter ([#5856](https://github.com/vector-im/element-android/issues/5856)) + - Revert: Use member name instead of room name in DM creation item ([#6032](https://github.com/vector-im/element-android/issues/6032)) + - Poll refactoring with unit tests ([#6074](https://github.com/vector-im/element-android/issues/6074)) + - Correct .well-known/matrix/client handling for server_names which include ports. ([#6095](https://github.com/vector-im/element-android/issues/6095)) + - Glide - Use current drawable while loading new static map image ([#6103](https://github.com/vector-im/element-android/issues/6103)) + - Fix sending multiple invites to a room reaching only one or two people ([#6109](https://github.com/vector-im/element-android/issues/6109)) + - Prevent widget web view from reloading on screen / orientation change ([#6140](https://github.com/vector-im/element-android/issues/6140)) + - Fix decrypting redacted event from sending errors ([#6148](https://github.com/vector-im/element-android/issues/6148)) + - Make widget web view request system permissions for camera and microphone (PSF-1061) ([#6149](https://github.com/vector-im/element-android/issues/6149)) + +In development 🚧 +---------------- + - Adds email input and verification screens to the new FTUE onboarding flow ([#5278](https://github.com/vector-im/element-android/issues/5278)) + - FTUE - Adds the redesigned Sign In screen ([#5283](https://github.com/vector-im/element-android/issues/5283)) + - [Live location sharing] Update message in timeline during the live ([#5689](https://github.com/vector-im/element-android/issues/5689)) + - FTUE - Overrides sign up flow ordering for matrix.org only ([#5783](https://github.com/vector-im/element-android/issues/5783)) + - Live location sharing: navigation from timeline to map screen + Live location sharing: show user pins on map screen ([#6012](https://github.com/vector-im/element-android/issues/6012)) + - FTUE - Adds homeserver login/register deeplink support ([#6023](https://github.com/vector-im/element-android/issues/6023)) + - [Live location sharing] Update entity in DB when a live is timed out ([#6123](https://github.com/vector-im/element-android/issues/6123)) + +SDK API changes ⚠️ +------------------ +- Notifies other devices when a verification request sent from an Android device is accepted.` ([#5724](https://github.com/vector-im/element-android/issues/5724)) +- Some `val` have been changed to `fun` to increase their visibility in the generated documentation. Just add `()` if you were using them. +- `KeysBackupService.state` has been replaced by `KeysBackupService.getState()` +- `KeysBackupService.isStucked` has been replaced by `KeysBackupService.isStuck()` +- SDK documentation improved ([#5952](https://github.com/vector-im/element-android/issues/5952)) +- Improve replay attacks and reduce duplicate message index errors ([#6077](https://github.com/vector-im/element-android/issues/6077)) +- Remove `RoomSummaryQueryParams.roomId`. If you need to observe a single room, use the new API `RoomService.getRoomSummaryLive(roomId: String)` +- `ActiveSpaceFilter` has been renamed to `SpaceFilter` +- `RoomCategoryFilter.ALL` has been removed, just pass `null` to not filter on Room category. ([#6143](https://github.com/vector-im/element-android/issues/6143)) + +Other changes +------------- + - leaving space experience changed to be aligned with iOS ([#5728](https://github.com/vector-im/element-android/issues/5728)) + - @Ignore a number of tests that are currently failing in CI. ([#6025](https://github.com/vector-im/element-android/issues/6025)) + - Remove ShortcutBadger lib and usage (it was dead code) ([#6041](https://github.com/vector-im/element-android/issues/6041)) + - Test: Ensure calling 'fail()' is not caught by the catch block ([#6089](https://github.com/vector-im/element-android/issues/6089)) + - Excludes transitive optional non FOSS google location dependency from fdroid builds ([#6100](https://github.com/vector-im/element-android/issues/6100)) + - Fixed grammar errors in /vector/src/main/res/values/strings.xml ([#6132](https://github.com/vector-im/element-android/issues/6132)) + - Downgrade gradle from 7.2.0 to 7.1.3 ([#6141](https://github.com/vector-im/element-android/issues/6141)) + - Add Lao language to the in-app settings. ([#6196](https://github.com/vector-im/element-android/issues/6196)) + - Remove the background location permission request ([#6198](https://github.com/vector-im/element-android/issues/6198)) + + Changes in Element v1.4.16 (2022-05-17) ======================================= diff --git a/README.md b/README.md index 54dfb7b288..7acb5aa638 100644 --- a/README.md +++ b/README.md @@ -1,9 +1,9 @@ [![Buildkite](https://badge.buildkite.com/ad0065c1b70f557cd3b1d3d68f9c2154010f83c4d6f71706a9.svg?branch=develop)](https://buildkite.com/matrix-dot-org/element-android/builds?branch=develop) [![Weblate](https://translate.element.io/widgets/element-android/-/svg-badge.svg)](https://translate.element.io/engage/element-android/?utm_source=widget) [![Element Android Matrix room #element-android:matrix.org](https://img.shields.io/matrix/element-android:matrix.org.svg?label=%23element-android:matrix.org&logo=matrix&server_fqdn=matrix.org)](https://matrix.to/#/#element-android:matrix.org) -[![Quality Gate](https://sonarcloud.io/api/project_badges/measure?project=im.vector.app.android&metric=alert_status)](https://sonarcloud.io/dashboard?id=im.vector.app.android) -[![Vulnerabilities](https://sonarcloud.io/api/project_badges/measure?project=im.vector.app.android&metric=vulnerabilities)](https://sonarcloud.io/dashboard?id=im.vector.app.android) -[![Bugs](https://sonarcloud.io/api/project_badges/measure?project=im.vector.app.android&metric=bugs)](https://sonarcloud.io/dashboard?id=im.vector.app.android) +[![Quality Gate Status](https://sonarcloud.io/api/project_badges/measure?project=vector-im_element-android&metric=alert_status)](https://sonarcloud.io/summary/new_code?id=vector-im_element-android) +[![Vulnerabilities](https://sonarcloud.io/api/project_badges/measure?project=vector-im_element-android&metric=vulnerabilities)](https://sonarcloud.io/summary/new_code?id=vector-im_element-android) +[![Bugs](https://sonarcloud.io/api/project_badges/measure?project=vector-im_element-android&metric=bugs)](https://sonarcloud.io/summary/new_code?id=vector-im_element-android) # Element Android diff --git a/build.gradle b/build.gradle index 25ff8b91c0..0244080ad0 100644 --- a/build.gradle +++ b/build.gradle @@ -25,11 +25,11 @@ buildscript { classpath libs.gradle.kotlinPlugin classpath libs.gradle.hiltPlugin classpath 'com.google.gms:google-services:4.3.10' - classpath 'org.sonarsource.scanner.gradle:sonarqube-gradle-plugin:3.3' + classpath 'org.sonarsource.scanner.gradle:sonarqube-gradle-plugin:3.4.0.2513' classpath 'com.google.android.gms:oss-licenses-plugin:0.10.5' classpath "com.likethesalad.android:stem-plugin:2.1.1" - classpath 'org.owasp:dependency-check-gradle:7.1.0.1' - classpath "org.jetbrains.dokka:dokka-gradle-plugin:1.6.21" + classpath 'org.owasp:dependency-check-gradle:7.1.1' + classpath "org.jetbrains.dokka:dokka-gradle-plugin:1.7.0" classpath "org.jetbrains.kotlinx:kotlinx-knit:0.4.0" // NOTE: Do not place your application dependencies here; they belong // in the individual module build.gradle files @@ -41,6 +41,9 @@ plugins { id "org.jlleitschuh.gradle.ktlint" version "10.3.0" // Detekt id "io.gitlab.arturbosch.detekt" version "1.20.0" + + // Dependency Analysis + id 'com.autonomousapps.dependency-analysis' version "1.9.0" } // https://github.com/jeremylong/DependencyCheck @@ -165,7 +168,7 @@ def launchTask = getGradle() .toString() .toLowerCase() -if (launchTask.contains("codeCoverageReport".toLowerCase())) { +if (launchTask.contains("coverage".toLowerCase())) { apply from: 'coverage.gradle' } @@ -177,8 +180,8 @@ apply plugin: 'org.sonarqube' sonarqube { properties { - property "sonar.projectName", "Element-Android" - property "sonar.projectKey", "im.vector.app.android" + property "sonar.projectName", "element-android" + property "sonar.projectKey", "vector-im_element-android" property "sonar.host.url", "https://sonarcloud.io" property "sonar.projectVersion", project(":vector").android.defaultConfig.versionName property "sonar.sourceEncoding", "UTF-8" @@ -188,7 +191,7 @@ sonarqube { property "sonar.links.issue", "https://github.com/vector-im/element-android/issues" property "sonar.organization", "new_vector_ltd_organization" property "sonar.java.coveragePlugin", "jacoco" - property "sonar.coverage.jacoco.xmlReportPaths", "${project.buildDir}/reports/jacoco/allCodeCoverageReport/allCodeCoverageReport.xml" + property "sonar.coverage.jacoco.xmlReportPaths", "${project.buildDir}/reports/jacoco/generateCoverageReport/generateCoverageReport.xml" property "sonar.login", project.hasProperty("SONAR_LOGIN") ? SONAR_LOGIN : "invalid" } } @@ -219,3 +222,59 @@ project(":library:diff-match-patch") { // } // } //} + +dependencyAnalysis { + dependencies { + bundle("kotlin-stdlib") { + includeGroup("org.jetbrains.kotlin") + } + bundle("react") { + includeGroup("com.facebook.react") + } + } + issues { + all { + ignoreKtx(true) + onUsedTransitiveDependencies { + // Transitively used dependencies that should be declared directly + severity("ignore") + } + onUnusedDependencies { + severity("fail") + } + onUnusedAnnotationProcessors { + severity("fail") + exclude("com.airbnb.android:epoxy-processor", "com.google.dagger:hilt-compiler") // False positives + } + } + project(":library:jsonviewer") { + onUnusedDependencies { + exclude("org.json:json") // Used in unit tests, overwrites the one bundled into Android + } + } + project(":library:ui-styles") + project(":matrix-sdk-android") { + onUnusedDependencies { + exclude("io.reactivex.rxjava2:rxkotlin") // Transitively required for mocking realm as monarchy doesn't expose Rx + } + } + project(":matrix-sdk-android-flow") { + onUnusedDependencies { + exclude("androidx.paging:paging-runtime-ktx") // False positive + } + } + project(":vector") { + onUnusedDependencies { + // False positives + exclude( + "androidx.fragment:fragment-testing", + "com.facebook.soloader:soloader", + "com.vanniktech:emoji-google", + "com.vanniktech:emoji-material", + "org.maplibre.gl:android-plugin-annotation-v9", + "org.maplibre.gl:android-sdk", + ) + } + } + } +} diff --git a/changelog.d/3448.feature b/changelog.d/3448.feature new file mode 100644 index 0000000000..3f83f1bef5 --- /dev/null +++ b/changelog.d/3448.feature @@ -0,0 +1 @@ +Use UnifiedPush and allows user to have push without FCM. diff --git a/changelog.d/5278.wip b/changelog.d/5278.wip deleted file mode 100644 index c6014dc9ac..0000000000 --- a/changelog.d/5278.wip +++ /dev/null @@ -1 +0,0 @@ -Adds email input and verification screens to the new FTUE onboarding flow diff --git a/changelog.d/5658.feature b/changelog.d/5658.feature deleted file mode 100644 index ba41a03207..0000000000 --- a/changelog.d/5658.feature +++ /dev/null @@ -1 +0,0 @@ -Space explore screen changes: removed space card, added rooms filtering diff --git a/changelog.d/5689.wip b/changelog.d/5689.wip deleted file mode 100644 index ccea1ec541..0000000000 --- a/changelog.d/5689.wip +++ /dev/null @@ -1 +0,0 @@ -[Live location sharing] Update message in timeline during the live diff --git a/changelog.d/5724.sdk b/changelog.d/5724.sdk deleted file mode 100644 index 5a0a37fe31..0000000000 --- a/changelog.d/5724.sdk +++ /dev/null @@ -1 +0,0 @@ -- Notifies other devices when a verification request sent from an Android device is accepted.` diff --git a/changelog.d/5728.misc b/changelog.d/5728.misc deleted file mode 100644 index 6e463fa76f..0000000000 --- a/changelog.d/5728.misc +++ /dev/null @@ -1 +0,0 @@ -leaving space experience changed to be aligned with iOS diff --git a/changelog.d/5821.bugfix b/changelog.d/5821.bugfix new file mode 100644 index 0000000000..25d8fd2b46 --- /dev/null +++ b/changelog.d/5821.bugfix @@ -0,0 +1 @@ +Fixes concurrent modification crash when signing out or launching the app diff --git a/changelog.d/5864.sdk b/changelog.d/5864.sdk new file mode 100644 index 0000000000..b0a9d1c67d --- /dev/null +++ b/changelog.d/5864.sdk @@ -0,0 +1 @@ +Group all location sharing related API into LocationSharingService diff --git a/changelog.d/5913.misc b/changelog.d/5913.misc new file mode 100644 index 0000000000..24be7194d5 --- /dev/null +++ b/changelog.d/5913.misc @@ -0,0 +1,3 @@ +- Notify of the latest known location in LocationTracker to avoid multiple locations at start +- Debounce location updates +- Improve location providers access diff --git a/changelog.d/6025.misc b/changelog.d/6025.misc deleted file mode 100644 index 2900796792..0000000000 --- a/changelog.d/6025.misc +++ /dev/null @@ -1 +0,0 @@ -@Ignore a number of tests that are currently failing in CI. diff --git a/changelog.d/6032.bugfix b/changelog.d/6032.bugfix deleted file mode 100644 index c20d7ddd08..0000000000 --- a/changelog.d/6032.bugfix +++ /dev/null @@ -1 +0,0 @@ -Revert: Use member name instead of room name in DM creation item diff --git a/changelog.d/6041.misc b/changelog.d/6041.misc deleted file mode 100644 index 50378ea3fd..0000000000 --- a/changelog.d/6041.misc +++ /dev/null @@ -1 +0,0 @@ -Remove ShortcutBadger lib and usage (it was dead code) diff --git a/changelog.d/6074.bugfix b/changelog.d/6074.bugfix deleted file mode 100644 index 692dce28d7..0000000000 --- a/changelog.d/6074.bugfix +++ /dev/null @@ -1 +0,0 @@ -Poll refactoring with unit tests diff --git a/changelog.d/6089.misc b/changelog.d/6089.misc deleted file mode 100644 index 19b951c1a3..0000000000 --- a/changelog.d/6089.misc +++ /dev/null @@ -1 +0,0 @@ -Test: Ensure calling 'fail()' is not caught by the catch block diff --git a/changelog.d/6095.bugfix b/changelog.d/6095.bugfix deleted file mode 100644 index 11110bfa08..0000000000 --- a/changelog.d/6095.bugfix +++ /dev/null @@ -1 +0,0 @@ -Correct .well-known/matrix/client handling for server_names which include ports. diff --git a/changelog.d/6098.feature b/changelog.d/6098.feature deleted file mode 100644 index 659da42094..0000000000 --- a/changelog.d/6098.feature +++ /dev/null @@ -1 +0,0 @@ -Labs flag for enabling live location sharing diff --git a/changelog.d/6101.bugfix b/changelog.d/6101.bugfix new file mode 100644 index 0000000000..2d8da5327d --- /dev/null +++ b/changelog.d/6101.bugfix @@ -0,0 +1 @@ +Refactor - better naming, return native user id and not sip user id and create a dm with the native user instead of with the sip user. diff --git a/changelog.d/6103.bugfix b/changelog.d/6103.bugfix deleted file mode 100644 index 12e6836460..0000000000 --- a/changelog.d/6103.bugfix +++ /dev/null @@ -1 +0,0 @@ -Glide - Use current drawable while loading new static map image diff --git a/changelog.d/6109.bugfix b/changelog.d/6109.bugfix deleted file mode 100644 index 43b1d610c7..0000000000 --- a/changelog.d/6109.bugfix +++ /dev/null @@ -1 +0,0 @@ -Fix sending multiple invites to a room reaching only one or two people diff --git a/changelog.d/6155.misc b/changelog.d/6155.misc new file mode 100644 index 0000000000..044e21408e --- /dev/null +++ b/changelog.d/6155.misc @@ -0,0 +1 @@ +Add unit tests for LiveLocationAggregationProcessor code diff --git a/changelog.d/6162.wip b/changelog.d/6162.wip new file mode 100644 index 0000000000..8b32a72571 --- /dev/null +++ b/changelog.d/6162.wip @@ -0,0 +1 @@ +FTUE - Adds automatic homeserver selection when typing a full matrix id during registration or login diff --git a/changelog.d/6191.sdk b/changelog.d/6191.sdk new file mode 100644 index 0000000000..d7fcf1b40f --- /dev/null +++ b/changelog.d/6191.sdk @@ -0,0 +1 @@ +Add support for MSC2457 - opting in or out of logging out all devices when changing password diff --git a/changelog.d/6203.feature b/changelog.d/6203.feature new file mode 100644 index 0000000000..bbfddd4179 --- /dev/null +++ b/changelog.d/6203.feature @@ -0,0 +1 @@ +Replace ffmpeg-kit with libopus and libopusenc. diff --git a/changelog.d/6217.feature b/changelog.d/6217.feature new file mode 100644 index 0000000000..6a8a31790f --- /dev/null +++ b/changelog.d/6217.feature @@ -0,0 +1 @@ +Improve lock screen implementation. diff --git a/changelog.d/6261.misc b/changelog.d/6261.misc new file mode 100644 index 0000000000..9ae8bf7b17 --- /dev/null +++ b/changelog.d/6261.misc @@ -0,0 +1 @@ +Making screenshots in bug reports opt in instead of opt out diff --git a/changelog.d/6285.feature b/changelog.d/6285.feature new file mode 100644 index 0000000000..ce88c13d15 --- /dev/null +++ b/changelog.d/6285.feature @@ -0,0 +1 @@ +Allow sharing text based content via android's share menu (eg .ics files) diff --git a/changelog.d/6290.bugfix b/changelog.d/6290.bugfix new file mode 100644 index 0000000000..e5ee3e02a6 --- /dev/null +++ b/changelog.d/6290.bugfix @@ -0,0 +1 @@ +Fixed crash when opening large images in the timeline diff --git a/changelog.d/6300.misc b/changelog.d/6300.misc new file mode 100644 index 0000000000..0ac762e595 --- /dev/null +++ b/changelog.d/6300.misc @@ -0,0 +1 @@ +Setup [Flipper](https://fbflipper.com/) diff --git a/changelog.d/6315.bugfix b/changelog.d/6315.bugfix new file mode 100644 index 0000000000..0b5eb6064d --- /dev/null +++ b/changelog.d/6315.bugfix @@ -0,0 +1 @@ +[Location sharing] Fix crash when starting/stopping a live when offline diff --git a/changelog.d/6318.bugfix b/changelog.d/6318.bugfix new file mode 100644 index 0000000000..9425c9a258 --- /dev/null +++ b/changelog.d/6318.bugfix @@ -0,0 +1 @@ +Fix loop in timeline and simplify management of chunks and timeline events. diff --git a/changelog.d/6319.sdk b/changelog.d/6319.sdk new file mode 100644 index 0000000000..3bf81c45b6 --- /dev/null +++ b/changelog.d/6319.sdk @@ -0,0 +1 @@ +Create `QueryStateEventValue` to do query on `stateKey` for State Event. Also remove the default parameter values for those type. diff --git a/changelog.d/6320.misc b/changelog.d/6320.misc new file mode 100644 index 0000000000..7cdd41f486 --- /dev/null +++ b/changelog.d/6320.misc @@ -0,0 +1 @@ +CreatePollViewModel unit tests diff --git a/changelog.d/6326.bugfix b/changelog.d/6326.bugfix new file mode 100644 index 0000000000..c09dd8fec1 --- /dev/null +++ b/changelog.d/6326.bugfix @@ -0,0 +1 @@ +Update design and behaviour on widget permission bottom sheet diff --git a/changelog.d/6328.bugfix b/changelog.d/6328.bugfix new file mode 100644 index 0000000000..7a41996e57 --- /dev/null +++ b/changelog.d/6328.bugfix @@ -0,0 +1 @@ +Fix | Some user verification requests couldn't be accepted/declined diff --git a/changelog.d/6329.misc b/changelog.d/6329.misc new file mode 100644 index 0000000000..dd87c11f6e --- /dev/null +++ b/changelog.d/6329.misc @@ -0,0 +1 @@ +Fix flaky test in voice recording feature. diff --git a/changelog.d/6349.bugfix b/changelog.d/6349.bugfix new file mode 100644 index 0000000000..70718248a7 --- /dev/null +++ b/changelog.d/6349.bugfix @@ -0,0 +1 @@ +[Location sharing] Fix stop of a live not possible from another device diff --git a/changelog.d/6357.bugfix b/changelog.d/6357.bugfix new file mode 100644 index 0000000000..231c65030f --- /dev/null +++ b/changelog.d/6357.bugfix @@ -0,0 +1 @@ +Fix backslash escapes in formatted messages diff --git a/changelog.d/6364.feature b/changelog.d/6364.feature new file mode 100644 index 0000000000..207d6d141b --- /dev/null +++ b/changelog.d/6364.feature @@ -0,0 +1 @@ +[Location sharing] - Stop any active live before starting a new one diff --git a/changelog.d/6366.misc b/changelog.d/6366.misc new file mode 100644 index 0000000000..5752b3d700 --- /dev/null +++ b/changelog.d/6366.misc @@ -0,0 +1 @@ +Poll view state unit tests diff --git a/changelog.d/6369.feature b/changelog.d/6369.feature new file mode 100644 index 0000000000..3c3e936dfd --- /dev/null +++ b/changelog.d/6369.feature @@ -0,0 +1,2 @@ + Expose pusher profile tag in advanced settings + \ No newline at end of file diff --git a/changelog.d/6371.bugfix b/changelog.d/6371.bugfix new file mode 100644 index 0000000000..275ec1cd8f --- /dev/null +++ b/changelog.d/6371.bugfix @@ -0,0 +1 @@ +Fixes wrong error message when signing in with wrong credentials diff --git a/changelog.d/6375.bugfix b/changelog.d/6375.bugfix new file mode 100644 index 0000000000..769ed81e69 --- /dev/null +++ b/changelog.d/6375.bugfix @@ -0,0 +1 @@ +[Location Share] - Adding missing prefix "u=" for uncertainty in geo URI diff --git a/changelog.d/6396.doc b/changelog.d/6396.doc new file mode 100644 index 0000000000..9b876d74af --- /dev/null +++ b/changelog.d/6396.doc @@ -0,0 +1 @@ +Update the PR process doc to come back to one reviewer with optional additional reviewers. \ No newline at end of file diff --git a/coverage.gradle b/coverage.gradle index b62ce0b4a0..f278a475ef 100644 --- a/coverage.gradle +++ b/coverage.gradle @@ -1,9 +1,36 @@ -def excludes = [ ] +def excludes = [ +// dependency injection graph +'**/*Module.*', +'**/*Module*.*', + +// Framework entry points +'**/*Activity*', +'**/*Fragment*', +'**/*Application*', + +// We would like to exclude android widgets as well but our naming is inconsistent + +// Proof of concept +'**/*Login2*', + +// Generated +'**/*JsonAdapter*', +'**/*Item.*', +'**/*$Holder.*', +'**/*ViewHolder.*', +'**/*View.*', +'**/*BottomSheet.*' +] def initializeReport(report, projects, classExcludes) { projects.each { project -> project.apply plugin: 'jacoco' } - report.executionData { fileTree(rootProject.rootDir.absolutePath).include("**/build/jacoco/*.exec") } + report.executionData { + fileTree(rootProject.rootDir.absolutePath).include( + "**/build/**/*.exec", + "**/build/outputs/code_coverage/**/coverage.ec", + ) + } report.reports { xml.enabled true html.enabled true @@ -43,13 +70,21 @@ def collectProjects(predicate) { return subprojects.findAll { it.buildFile.isFile() && predicate(it) } } -task allCodeCoverageReport(type: JacocoReport) { +task generateCoverageReport(type: JacocoReport) { outputs.upToDateWhen { false } rootProject.apply plugin: 'jacoco' - // to limit projects in a specific report, add - // def excludedProjects = [ ... ] - // def projects = collectProjects { !excludedProjects.contains(it.name) } - def projects = collectProjects { true } - dependsOn { projects*.test } + def projects = collectProjects { ['vector', 'matrix-sdk-android'].contains(it.name) } initializeReport(it, projects, excludes) } + +task unitTestsWithCoverage(type: GradleBuild) { + // the 7.1.3 android gradle plugin has a bug where enableTestCoverage generates invalid coverage + startParameter.projectProperties.coverage = [enableTestCoverage: false] + tasks = [':vector:testGplayDebugUnitTest', ':matrix-sdk-android:testDebugUnitTest'] +} + +task instrumentationTestsWithCoverage(type: GradleBuild) { + startParameter.projectProperties.coverage = [enableTestCoverage: true] + startParameter.projectProperties['android.testInstrumentationRunnerArguments.notPackage'] = 'im.vector.app.ui' + tasks = [':vector:connectedGplayDebugAndroidTest', 'matrix-sdk-android:connectedDebugAndroidTest'] +} diff --git a/dependencies.gradle b/dependencies.gradle index 10f9539e5a..db9278b975 100644 --- a/dependencies.gradle +++ b/dependencies.gradle @@ -7,10 +7,13 @@ ext.versions = [ 'targetCompat' : JavaVersion.VERSION_11, ] -def gradle = "7.2.0" + +// Pinned to 7.1.3 because of https://github.com/vector-im/element-android/issues/6142 +// Please test carefully before upgrading again. +def gradle = "7.1.3" // Ref: https://kotlinlang.org/releases.html def kotlin = "1.6.21" -def kotlinCoroutines = "1.6.1" +def kotlinCoroutines = "1.6.3" def dagger = "2.42" def retrofit = "2.9.0" def arrow = "0.8.2" @@ -18,20 +21,21 @@ def markwon = "4.6.2" def moshi = "1.13.0" def lifecycle = "2.4.1" def flowBinding = "1.2.0" +def flipper = "0.151.1" def epoxy = "4.6.2" -def mavericks = "2.6.1" +def mavericks = "2.7.0" def glide = "4.13.2" def bigImageViewer = "1.8.1" def jjwt = "0.11.5" -def vanniktechEmoji = "0.9.0" +def vanniktechEmoji = "0.15.0" + +def fragment = "1.4.1" // Testing -def mockk = "1.12.4" +def mockk = "1.12.3" // We need to use 1.12.3 to have mocking in androidTest until a new version is released: https://github.com/mockk/mockk/issues/819 def espresso = "3.4.0" def androidxTest = "1.4.0" def androidxOrchestrator = "1.4.1" - - ext.libs = [ gradle : [ 'gradlePlugin' : "com.android.tools.build:gradle:$gradle", @@ -45,12 +49,17 @@ ext.libs = [ 'coroutinesTest' : "org.jetbrains.kotlinx:kotlinx-coroutines-test:$kotlinCoroutines" ], androidx : [ - 'appCompat' : "androidx.appcompat:appcompat:1.4.1", - 'core' : "androidx.core:core-ktx:1.7.0", + 'annotation' : "androidx.annotation:annotation:1.4.0", + 'activity' : "androidx.activity:activity:1.4.0", + 'annotations' : "androidx.annotation:annotation:1.3.0", + 'appCompat' : "androidx.appcompat:appcompat:1.4.2", + 'biometric' : "androidx.biometric:biometric:1.1.0", + 'core' : "androidx.core:core-ktx:1.8.0", 'recyclerview' : "androidx.recyclerview:recyclerview:1.2.1", 'exifinterface' : "androidx.exifinterface:exifinterface:1.3.3", - 'fragmentKtx' : "androidx.fragment:fragment-ktx:1.4.1", - 'constraintLayout' : "androidx.constraintlayout:constraintlayout:2.1.3", + 'fragmentKtx' : "androidx.fragment:fragment-ktx:$fragment", + 'fragmentTesting' : "androidx.fragment:fragment-testing:$fragment", + 'constraintLayout' : "androidx.constraintlayout:constraintlayout:2.1.4", 'work' : "androidx.work:work-runtime-ktx:2.7.1", 'autoFill' : "androidx.autofill:autofill:1.1.0", 'preferenceKtx' : "androidx.preference:preference-ktx:1.2.0", @@ -69,19 +78,27 @@ ext.libs = [ 'testRules' : "androidx.test:rules:$androidxTest", 'espressoCore' : "androidx.test.espresso:espresso-core:$espresso", 'espressoContrib' : "androidx.test.espresso:espresso-contrib:$espresso", - 'espressoIntents' : "androidx.test.espresso:espresso-intents:$espresso" + 'espressoIntents' : "androidx.test.espresso:espresso-intents:$espresso", + 'viewpager2' : "androidx.viewpager2:viewpager2:1.0.0", + 'transition' : "androidx.transition:transition:1.2.0", ], google : [ - 'material' : "com.google.android.material:material:1.6.0" + 'material' : "com.google.android.material:material:1.6.1" ], dagger : [ 'dagger' : "com.google.dagger:dagger:$dagger", 'daggerCompiler' : "com.google.dagger:dagger-compiler:$dagger", 'hilt' : "com.google.dagger:hilt-android:$dagger", + 'hiltAndroidTesting' : "com.google.dagger:hilt-android-testing:$dagger", 'hiltCompiler' : "com.google.dagger:hilt-compiler:$dagger" ], + flipper : [ + 'flipper' : "com.facebook.flipper:flipper:$flipper", + 'flipperNetworkPlugin' : "com.facebook.flipper:flipper-network-plugin:$flipper", + ], squareup : [ - 'moshi' : "com.squareup.moshi:moshi-adapters:$moshi", + 'moshi' : "com.squareup.moshi:moshi:$moshi", + 'moshiKt' : "com.squareup.moshi:moshi-kotlin:$moshi", 'moshiKotlin' : "com.squareup.moshi:moshi-kotlin-codegen:$moshi", 'retrofit' : "com.squareup.retrofit2:retrofit:$retrofit", 'retrofitMoshi' : "com.squareup.retrofit2:converter-moshi:$retrofit" @@ -107,6 +124,10 @@ ext.libs = [ 'mavericks' : "com.airbnb.android:mavericks:$mavericks", 'mavericksTesting' : "com.airbnb.android:mavericks-testing:$mavericks" ], + maplibre : [ + 'androidSdk' : "org.maplibre.gl:android-sdk:9.5.2", + 'pluginAnnotation' : "org.maplibre.gl:android-plugin-annotation-v9:1.0.0" + ], mockk : [ 'mockk' : "io.mockk:mockk:$mockk", 'mockkAndroid' : "io.mockk:mockk-android:$mockk" @@ -143,3 +164,5 @@ ext.libs = [ 'junit' : "junit:junit:4.13.2" ] ] + + diff --git a/dependencies_groups.gradle b/dependencies_groups.gradle index 59cefe7e89..b785c7f50b 100644 --- a/dependencies_groups.gradle +++ b/dependencies_groups.gradle @@ -9,6 +9,7 @@ ext.groups = [ 'com.github.jetradarmobile', 'com.github.MatrixFrog', 'com.github.tapadoo', + 'com.github.UnifiedPush', 'com.github.vector-im', 'com.github.yalantis', 'com.github.Zhuinden', @@ -31,6 +32,7 @@ ext.groups = [ ], group: [ 'com.android', + 'com.android.ndk.thirdparty', 'com.android.tools', 'com.google.firebase', 'com.google.testing.platform', @@ -52,6 +54,7 @@ ext.groups = [ 'com.dropbox.core', 'com.soywiz.korlibs.korte', 'com.facebook.fbjni', + 'com.facebook.flipper', 'com.facebook.fresco', 'com.facebook.infer.annotation', 'com.facebook.soloader', @@ -93,6 +96,7 @@ ext.groups = [ 'com.ibm.icu', 'com.jakewharton.android.repackaged', 'com.jakewharton.timber', + 'com.kgurgul.flipper', 'com.linkedin.dexmaker', 'com.mapbox.mapboxsdk', 'com.nulab-inc', @@ -168,6 +172,7 @@ ext.groups = [ 'org.glassfish.jaxb', 'org.hamcrest', 'org.jacoco', + 'org.java-websocket', 'org.jetbrains', 'org.jetbrains.dokka', 'org.jetbrains.intellij.deps', diff --git a/docs/add_threePids.md b/docs/add_threePids.md index 6fb0aff426..8d02702ca6 100644 --- a/docs/add_threePids.md +++ b/docs/add_threePids.md @@ -1,5 +1,34 @@ # Adding and removing ThreePids to an account + + +* [Add email](#add-email) + * [User enter the email](#user-enter-the-email) + * [The email is already added to an account](#the-email-is-already-added-to-an-account) + * [The email is free](#the-email-is-free) +* [User receives an e-mail](#user-receives-an-e-mail) + * [User clicks on the link](#user-clicks-on-the-link) + * [User returns on Element](#user-returns-on-element) + * [User enters his password](#user-enters-his-password) + * [The link has not been clicked](#the-link-has-not-been-clicked) + * [Wrong password](#wrong-password) + * [The link has been clicked and the account password is correct](#the-link-has-been-clicked-and-the-account-password-is-correct) +* [Remove email](#remove-email) + * [User want to remove an email from his account](#user-want-to-remove-an-email-from-his-account) + * [Email was not bound to an identity server](#email-was-not-bound-to-an-identity-server) + * [Email was bound to an identity server](#email-was-bound-to-an-identity-server) +* [Add phone number](#add-phone-number) + * [The phone number is already added to an account](#the-phone-number-is-already-added-to-an-account) + * [The phone number is free](#the-phone-number-is-free) +* [User receive a text message](#user-receive-a-text-message) + * [User enter the code to the app](#user-enter-the-code-to-the-app) + * [Wrong code](#wrong-code) + * [Correct code](#correct-code) +* [Remove phone number](#remove-phone-number) + * [User wants to remove a phone number from his account](#user-wants-to-remove-a-phone-number-from-his-account) + + + ## Add email ### User enter the email diff --git a/docs/analytics.md b/docs/analytics.md index 135ace81b0..9b2c176912 100644 --- a/docs/analytics.md +++ b/docs/analytics.md @@ -1,5 +1,13 @@ # Analytics in Element + + +* [Solution](#solution) +* [How to add a new Event](#how-to-add-a-new-event) +* [Forks of Element](#forks-of-element) + + + ## Solution Element is using PostHog to send analytics event. diff --git a/docs/color_migration_guide.md b/docs/color_migration_guide.md index 31a531d124..f83a88e2b2 100644 --- a/docs/color_migration_guide.md +++ b/docs/color_migration_guide.md @@ -1,5 +1,14 @@ # Color migration + + + * [Changes](#changes) + * [Main change for developers](#main-change-for-developers) + * [Remaining work](#remaining-work) + * [Migration guide](#migration-guide) + + + ### Changes - use colors defined in https://www.figma.com/file/X4XTH9iS2KGJ2wFKDqkyed/Compound?node-id=557%3A0 diff --git a/docs/design.md b/docs/design.md index a79f19cf3e..f390725560 100644 --- a/docs/design.md +++ b/docs/design.md @@ -1,5 +1,31 @@ # Element Android design + + +* [Introduction](#introduction) +* [How to import from Figma to the Element Android project](#how-to-import-from-figma-to-the-element-android-project) + * [Colors](#colors) + * [Text](#text) + * [Dimension, position and margin](#dimension-position-and-margin) + * [Icons](#icons) + * [Export drawable from Figma](#export-drawable-from-figma) + * [Import in Android Studio](#import-in-android-studio) + * [Images](#images) +* [Figma links](#figma-links) + * [Coumpound](#coumpound) + * [Login](#login) + * [Login v2](#login-v2) + * [Room list](#room-list) + * [Timeline](#timeline) + * [Voice message](#voice-message) + * [Room settings](#room-settings) + * [VoIP](#voip) + * [Presence](#presence) + * [Spaces](#spaces) + * [List to be continued...](#list-to-be-continued) + + + ## Introduction Design at element.io is done using Figma - https://www.figma.com diff --git a/docs/flipper.md b/docs/flipper.md new file mode 100644 index 0000000000..495425ce5f --- /dev/null +++ b/docs/flipper.md @@ -0,0 +1,58 @@ +# Flipper + + + +* [Introduction](#introduction) +* [Setup](#setup) + * [Troubleshoot](#troubleshoot) + * [No device found issue](#no-device-found-issue) + * [Diagnostic Activity](#diagnostic-activity) + * [Other](#other) +* [Links](#links) + + + +## Introduction + +[Flipper](https://fbflipper.com) is a powerful tool from Meta, which allow to inspect the running application details and states from your computer. + +Flipper is configured in the Element Android project to let the developers be able to: +- inspect all the Realm databases content; +- do layout inspection; +- see the crash logs; +- see the logcat; +- see all the network requests; +- see all the SharedPreferences; +- take screenshots and record videos of the device; +- and more! + +## Setup + +- Install Flipper on your computer. Follow instructions here: https://fbflipper.com/docs/getting-started/index/ +- Run the debug version of Element on an emulator or on a real device. + +### Troubleshoot + +#### No device found issue + +The configuration of the Flipper application has to be updated. The issue has been asked and answered here: https://stackoverflow.com/questions/71744103/android-emulator-unable-to-connect-to-flipper/72608113#72608113 + +#### Diagnostic Activity + +Flipper comes with a Diagnostic Activity that you can start from command line using: + +```shell +adb shell am start -n im.vector.app.debug/com.facebook.flipper.android.diagnostics.FlipperDiagnosticActivity +``` + +It provides some log which can help to figure out what's going on client side. + +#### Other + +https://fbflipper.com/docs/getting-started/troubleshooting/android/ may help. + +## Links + +- Official Flipper website: https://fbflipper.com +- Realm Plugin for Flipper: https://github.com/kamgurgul/Flipper-Realm +- Dedicated Matrix room: https://matrix.to/#/#unifiedpush:matrix.org diff --git a/docs/identity_server.md b/docs/identity_server.md index e765ae3949..a1ee86cb19 100644 --- a/docs/identity_server.md +++ b/docs/identity_server.md @@ -1,5 +1,19 @@ # Identity server + + +* [Introduction](#introduction) +* [Implementation](#implementation) +* [Related MSCs](#related-mscs) +* [Steps and requirements](#steps-and-requirements) +* [Screens](#screens) + * [Settings](#settings) + * [Discovery screen](#discovery-screen) + * [Set identity server screen](#set-identity-server-screen) +* [Ref:](#ref:) + + + Issue: #607 PR: #1354 diff --git a/docs/integration_tests.md b/docs/integration_tests.md index e79f966d1f..b5a830e7ff 100644 --- a/docs/integration_tests.md +++ b/docs/integration_tests.md @@ -1,5 +1,18 @@ # Integration tests + + +* [Pre requirements](#pre-requirements) +* [Install and run Synapse](#install-and-run-synapse) +* [Run the test](#run-the-test) +* [Stop Synapse](#stop-synapse) +* [Troubleshoot](#troubleshoot) + * [Android Emulator does cannot reach the homeserver](#android-emulator-does-cannot-reach-the-homeserver) + * [Tests partially run but some fail with "Unable to contact localhost:8080"](#tests-partially-run-but-some-fail-with-"unable-to-contact-localhost:8080") + * [virtualenv command fails](#virtualenv-command-fails) + + + Integration tests are useful to ensure that the code works well for any use cases. They can also be used as sample on how to use the Matrix SDK. diff --git a/docs/jitsi.md b/docs/jitsi.md index 1b4e0a37b4..4dd06effdb 100644 --- a/docs/jitsi.md +++ b/docs/jitsi.md @@ -1,20 +1,32 @@ # Jitsi in Element Android + + +* [Native Jitsi SDK](#native-jitsi-sdk) + * [How to build the Jitsi Meet SDK](#how-to-build-the-jitsi-meet-sdk) + * [Jitsi version](#jitsi-version) + * [Run the build script](#run-the-build-script) + * [Link with the new generated library](#link-with-the-new-generated-library) + * [Sanity tests](#sanity-tests) + * [Export the build library](#export-the-build-library) + + + Native Jitsi support has been added to Element Android by the PR [#1914](https://github.com/vector-im/element-android/pull/1914). The description of the PR contains some documentation about the behaviour in each possible room configuration. Also, ensure to have a look on [the documentation from Element Web](https://github.com/vector-im/element-web/blob/develop/docs/jitsi.md) The official documentation about how to integrate the Jitsi SDK in an Android app is available here: https://jitsi.github.io/handbook/docs/dev-guide/dev-guide-android-sdk. -# Native Jitsi SDK +## Native Jitsi SDK The Jitsi SDK is built by ourselves with the flag LIBRE_BUILD, to be able to be integrated on the F-Droid version of Element Android. The generated maven repository is then host in the project https://github.com/vector-im/jitsi_libre_maven -## How to build the Jitsi Meet SDK +### How to build the Jitsi Meet SDK -### Jitsi version +#### Jitsi version Update the script `./tools/jitsi/build_jisti_libs.sh` with the tag of the project `https://github.com/jitsi/jitsi-meet`. @@ -22,7 +34,7 @@ Latest tag can be found from this page: https://github.com/jitsi/jitsi-meet-rele Currently we are building the version with the tag `android-sdk-3.10.0`. -### Run the build script +#### Run the build script At the root of the Element Android, run the following script: @@ -32,7 +44,7 @@ At the root of the Element Android, run the following script: It will build the Jitsi Meet Android library and put every generated files in the folder `/tmp/jitsi` -### Link with the new generated library +#### Link with the new generated library - Update the file `./build.gradle` to use the previously created local Maven repository. Currently we have this line: @@ -57,7 +69,7 @@ implementation('com.facebook.react:react-native-webrtc:1.92.1-jitsi-9093212@aar' - Perform a gradle sync and build the project - Perform test -### Sanity tests +#### Sanity tests In order to validate that the upgrade of the Jitsi and WebRTC dependency does not break anything, the following sanity tests have to be performed, using two devices: - Make 1-1 audio call (so using WebRTC) @@ -65,7 +77,7 @@ In order to validate that the upgrade of the Jitsi and WebRTC dependency does no - Create and join a conference call with audio only (so using Jitsi library). Leave the conference. Join it again. - Create and join a conference call with audio and video (so using Jitsi library) Leave the conference. Join it again. -### Export the build library +#### Export the build library If all the tests are passed, you can export the generated Jitsi library to our Maven repository. @@ -81,4 +93,4 @@ url "https://github.com/vector-im/jitsi_libre_maven/raw/master/android-sdk-3.10. - Build the project and perform the sanity tests again. -- Update the file `/CHANGES.md` to notify about the library upgrade, and create a regular PR for project Element Android. \ No newline at end of file +- Update the file `/CHANGES.md` to notify about the library upgrade, and create a regular PR for project Element Android. diff --git a/docs/notifications.md b/docs/notifications.md index b44984785a..612b8785b8 100644 --- a/docs/notifications.md +++ b/docs/notifications.md @@ -1,37 +1,42 @@ This document aims to describe how Element android displays notifications to the end user. It also clarifies notifications and background settings in the app. # Table of Contents -1. [Prerequisites Knowledge](#prerequisites-knowledge) - * [How does a matrix client get a message from a homeserver?](#how-does-a-matrix-client-get-a-message-from-a-homeserver) - * [How does a mobile app receives push notification?](#how-does-a-mobile-app-receives-push-notification) - * [Push VS Notification](#push-vs-notification) - * [Push in the matrix federated world](#push-in-the-matrix-federated-world) - * [How does the homeserver know when to notify a client?](#how-does-the-homeserver-know-when-to-notify-a-client) - * [Push vs privacy, and mitigation](#push-vs-privacy-and-mitigation) - * [Background processing limitations](#background-processing-limitations) -2. [Element Notification implementations](#element-notification-implementations) - * [Requirements](#requirements) - * [Foreground sync mode (Gplay & F-Droid)](#foreground-sync-mode-gplay-f-droid) - * [Push (FCM) received in background](#push-fcm-received-in-background) - * [FCM Fallback mode](#fcm-fallback-mode) - * [F-Droid background Mode](#f-droid-background-mode) -3. [Application Settings](#application-settings) + + + +* [Prerequisites Knowledge](#prerequisites-knowledge) + * [How does a matrix client get a message from a homeserver?](#how-does-a-matrix-client-get-a-message-from-a-homeserver?) + * [How does a mobile app receives push notification](#how-does-a-mobile-app-receives-push-notification) + * [Push VS Notification](#push-vs-notification) + * [Push in the matrix federated world](#push-in-the-matrix-federated-world) + * [How does the homeserver know when to notify a client?](#how-does-the-homeserver-know-when-to-notify-a-client?) + * [Push vs privacy, and mitigation](#push-vs-privacy-and-mitigation) + * [Background processing limitations](#background-processing-limitations) +* [Element Notification implementations](#element-notification-implementations) + * [Requirements](#requirements) + * [Foreground sync mode (Gplay and F-Droid)](#foreground-sync-mode-gplay-and-f-droid) + * [Push (FCM) received in background](#push-fcm-received-in-background) + * [FCM Fallback mode](#fcm-fallback-mode) + * [F-Droid background Mode](#f-droid-background-mode) +* [Application Settings](#application-settings) + + First let's start with some prerequisite knowledge -# Prerequisites Knowledge +## Prerequisites Knowledge -## How does a matrix client get a message from a homeserver? +### How does a matrix client get a message from a homeserver? In order to get messages from a homeserver, a matrix client need to perform a ``sync`` operation. `To read events, the intended flow of operation is for clients to first call the /sync API without a since parameter. This returns the most recent message events for each room, as well as the state of the room at the start of the returned timeline. ` -The client need to call the `sync`API periodically in order to get incremental updates of the server state (new messages). +The client need to call the `sync` API periodically in order to get incremental updates of the server state (new messages). This mechanism is known as **HTTP long Polling**. -Using the **HTTP Long Polling** mechanism a client polls a server requesting new information. +Using the **HTTP Long Polling** mechanism a client polls a server requesting new information. The server *holds the request open until new data is available*. Once available, the server responds and sends the new information. When the client receives the new information, it immediately sends another request, and the operation is repeated. @@ -52,7 +57,7 @@ By default, this is 0, so the server will return immediately even if the respons When the Element Android app is open (i.e in foreground state), the default timeout is 30 seconds, and delay is 0. -## How does a mobile app receives push notification +### How does a mobile app receives push notification Push notification is used as a way to wake up a mobile application when some important information is available and should be processed. @@ -66,22 +71,22 @@ FCM will only work on android devices that have Google plays services installed (In simple terms, Google Play Services is a background service that runs on Android, which in turn helps in integrating Google’s advanced functionalities to other applications) De-Googlified devices need to rely on something else in order to stay up to date with a server. -There some cases when devices with google services cannot use FCM (network infrastructure limitations -firewalls- , - privacy and or independency requirement, source code licence) +There some cases when devices with google services cannot use FCM (network infrastructure limitations -firewalls-, + privacy and or independence requirement, source code licence) -## Push VS Notification +### Push VS Notification This need some disambiguation, because it is the source of common confusion: -*The fact that you see a notification on your screen does not mean that you have successfully configured your PUSH plateform.* +*The fact that you see a notification on your screen does not mean that you have successfully configured your PUSH platform.* Technically there is a difference between a push and a notification. A notification is what you see on screen and/or in the notification Menu/Drawer (in the top bar of the phone). Notifications are not always triggered by a push (One can display a notification locally triggered by an alarm) -## Push in the matrix federated world +### Push in the matrix federated world In order to send a push to a mobile, App developers need to have a server that will use the FCM APIs, and these APIs requires authentication! This server is called a **Push Gateway** in the matrix world @@ -118,11 +123,11 @@ Client/Server API + | | | | | ``` Recommended reading: - * https://thomask.sdf.org/blog/2016/12/11/riots-magical-push-notifications-in-ios.html +* https://thomask.sdf.org/blog/2016/12/11/riots-magical-push-notifications-in-ios.html * https://matrix.org/docs/spec/client_server/r0.4.0.html#id128 -## How does the homeserver know when to notify a client? +### How does the homeserver know when to notify a client? This is defined by [**push rules**](https://matrix.org/docs/spec/client_server/r0.4.0.html#push-rules-). @@ -140,14 +145,14 @@ Of course, content patterns matching cannot be used for encrypted messages serve That is why clients are able to **process the push rules client side** to decide what kind of notification should be presented for a given event. -## Push vs privacy, and mitigation +### Push vs privacy, and mitigation As seen previously, App developers don't directly send a push to the end user's device, they use a Push Provider as intermediary. So technically this intermediary is able to read the content of what is sent. App developers usually mitigate this by sending a `silent notification`, that is a notification with no identifiable data, or with an encrypted payload. When the push is received the app can then synchronise to it's server in order to generate a local notification. -## Background processing limitations +### Background processing limitations A mobile applications process live in a managed word, meaning that its process can be limited (e.g no network access), stopped or killed at almost anytime by the Operating System. @@ -167,15 +172,15 @@ The documentation on this subject is vague, and as per our experiments not alway It is getting more and more complex to have reliable notifications when FCM is not used. -# Element Notification implementations +## Element Notification implementations -## Requirements +### Requirements Element Android must work with and without FCM. * The Element android app published on F-Droid do not rely on FCM (all related dependencies are not present) * The Element android app published on google play rely on FCM, with a fallback mode when FCM registration has failed (e.g outdated or missing Google Play Services) -## Foreground sync mode (Gplay & F-Droid) +### Foreground sync mode (Gplay and F-Droid) When in foreground, Element performs sync continuously with a timeout value set to 10 seconds (see HttpPooling). @@ -183,9 +188,9 @@ As this mode does not need to live beyond the scope of the application, and as p This mode is turned on when the app enters foreground, and off when enters background. -In background, and depending on wether push is available or not, Element will use different methods to perform the syncs (Workers / Alarms / Service) +In background, and depending on whether push is available or not, Element will use different methods to perform the syncs (Workers / Alarms / Service) -## Push (FCM) received in background +### Push (FCM) received in background In order to enable Push, Element must first get a push token from the firebase SDK, then register a pusher with this token on the homeserver. @@ -225,10 +230,10 @@ Upon reception of the FCM push, Element will perform a sync call to the homeserv Element implements several strategies in these cases (TODO document) -## FCM Fallback mode +### FCM Fallback mode It is possible that Element is not able to get a FCM push token. -Common errors (amoung several others) that can cause that: +Common errors (among several others) that can cause that: * Google Play Services is outdated * Google Play Service fails in someways with FCM servers (infamous `SERVICE_NOT_AVAILABLE`) @@ -246,7 +251,7 @@ Usually in this mode, what happen is when you take back your phone in your hand, The fallback mode is supposed to be a temporary state waiting for the user to fix issues for FCM, or for App Developers that has done a fork to correctly configure their FCM settings. -## F-Droid background Mode +### F-Droid background Mode The F-Droid Element flavor has no dependencies to FCM, therefore cannot relies on Push. @@ -256,7 +261,7 @@ Only solution left is to use `AlarmManager`, that offers new API to allow launch Notice that these alarms, due to their potential impact on battery life, can still be restricted by the system. Documentation says that they will not be triggered more than every minutes under normal system operation, and when in low power mode about every 15 mn. -These restrictions can be relaxed by requirering the app to be white listed from battery optimization. +These restrictions can be relaxed by requiring the app to be white listed from battery optimization. F-Droid version will schedule alarms that will then trigger a Broadcast Receiver, that in turn will launch a Service (in the classic android way), and the reschedule an alarm for next time. @@ -266,9 +271,7 @@ That is why on Element F-Droid, the broadcast receiver will acquire a temporary Note that foreground services require to put a notification informing the user that the app is doing something even if not launched). - - -# Application Settings +## Application Settings **Notifications > Enable notifications for this account** diff --git a/docs/pull_request.md b/docs/pull_request.md index 94f8f3b6d8..eebf2814a9 100644 --- a/docs/pull_request.md +++ b/docs/pull_request.md @@ -1,5 +1,43 @@ # Pull requests + + +* [Introduction](#introduction) +* [Who should read this document?](#who-should-read-this-document?) +* [Submitting PR](#submitting-pr) + * [Who can submit pull requests?](#who-can-submit-pull-requests?) + * [Humans](#humans) + * [Draft PR?](#draft-pr?) + * [Base branch](#base-branch) + * [PR Review Assignment](#pr-review-assignment) + * [PR review time](#pr-review-time) + * [Re-request PR review](#re-request-pr-review) + * [When create split PR?](#when-create-split-pr?) + * [Avoid fixing other unrelated issue in a big PR](#avoid-fixing-other-unrelated-issue-in-a-big-pr) + * [Bots](#bots) + * [Dependabot](#dependabot) + * [Gradle wrapper](#gradle-wrapper) + * [Sync analytics plan](#sync-analytics-plan) +* [Reviewing PR](#reviewing-pr) + * [Who can review pull requests?](#who-can-review-pull-requests?) + * [What to have in mind when reviewing a PR](#what-to-have-in-mind-when-reviewing-a-pr) + * [Rules](#rules) + * [Check the form](#check-the-form) + * [PR title](#pr-title) + * [PR description](#pr-description) + * [File change](#file-change) + * [Check the commit](#check-the-commit) + * [Check the substance](#check-the-substance) + * [Make a dedicated meeting to review the PR](#make-a-dedicated-meeting-to-review-the-pr) + * [What happen to the issue(s)?](#what-happen-to-the-issues?) + * [Merge conflict](#merge-conflict) + * [When and who can merge PR](#when-and-who-can-merge-pr) + * [Merge type](#merge-type) + * [Resolve conversation](#resolve-conversation) +* [Responsibility](#responsibility) + + + ## Introduction This document gives some clue about how to efficiently manage Pull Requests (PR). This document is a first draft and may be improved later. @@ -45,15 +83,16 @@ Exceptions can occur: ##### PR Review Assignment -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: +We use automatic assignment for PR reviews. **A PR is automatically routed by GitHub to one team member** using the round robin algorithm. Additional reviewers can be used for complex changes or when the first reviewer is not confident enough on the changes. +The process is the following: -- 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. +- The PR creator selects the [element-android-reviewers](https://github.com/orgs/vector-im/teams/element-android-reviewers) team as a reviewer. +- GitHub automatically assign the reviewer. If the reviewer is not available (holiday, etc.), remove them and set again the team, GitHub will select another reviewer. +- Alternatively, the PR creator can directly assign specific people if they have another Android developer in their team or they think a specific reviewer should take a look at their PR. - 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-reviewers](https://github.com/orgs/vector-im/teams/element-android-reviewers) or any members 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 member directly. ##### PR review time @@ -64,6 +103,7 @@ Some tips to achieve it: - Set up your GH notifications correctly - Check your pulls page: [https://github.com/pulls](https://github.com/pulls) - Check your pending assigned PRs before starting or resuming your day to day tasks +- If you are busy with high priority tasks, inform the author. They will find another developer It is hard to define a deadline for a review. It depends on the PR size and the complexity. Let's start with a goal of 24h (working day!) for a PR smaller than 500 lines. If bigger, the submitter and the reviewer should discuss. diff --git a/docs/signin.md b/docs/signin.md index 0a234d2a20..65e305389f 100644 --- a/docs/signin.md +++ b/docs/signin.md @@ -2,6 +2,27 @@ This document describes the flow of signin to a homeserver, and also the flow when user want to reset his password. Examples come from the `matrix.org` homeserver. + + +* [Sign in flows](#sign-in-flows) + * [Get the flow](#get-the-flow) + * [Login with username](#login-with-username) + * [Incorrect password](#incorrect-password) + * [Correct password:](#correct-password:) + * [Login with email](#login-with-email) + * [Unknown email](#unknown-email) + * [Known email, wrong password](#known-email-wrong-password) + * [Known email, correct password](#known-email-correct-password) + * [Login with Msisdn](#login-with-msisdn) + * [Login with SSO](#login-with-sso) +* [Reset password](#reset-password) + * [Send email](#send-email) + * [When the email is not known](#when-the-email-is-not-known) + * [When the email is known](#when-the-email-is-known) + * [User clicks on the link](#user-clicks-on-the-link) + + + ## Sign in flows ### Get the flow @@ -322,4 +343,4 @@ curl -X POST --data $'{"auth":{"type":"m.login.email.identity","threepid_creds": {} ``` -The password has been changed, and all the existing token are invalidated. User can now login with the new password. \ No newline at end of file +The password has been changed, and all the existing token are invalidated. User can now login with the new password. diff --git a/docs/signup.md b/docs/signup.md index 97cd20a423..0b01338965 100644 --- a/docs/signup.md +++ b/docs/signup.md @@ -4,6 +4,20 @@ This document describes the flow of registration to a homeserver. Examples come *Ref*: https://matrix.org/docs/spec/client_server/latest#account-registration-and-management + + +* [Sign up flows](#sign-up-flows) + * [First step](#first-step) + * [Step 1: entering user name and password](#step-1:-entering-user-name-and-password) + * [If username already exists](#if-username-already-exists) + * [Step 2: entering email](#step-2:-entering-email) + * [Step 2 bis: user enters an email](#step-2-bis:-user-enters-an-email) + * [Step 3: Accepting T&C](#step-3:-accepting-t&c) + * [Step 4: Captcha](#step-4:-captcha) + * [Step 5: MSISDN](#step-5:-msisdn) + + + ## Sign up flows ### First step diff --git a/docs/ui-tests.md b/docs/ui-tests.md index 667a6ed7fb..d0b9db6818 100644 --- a/docs/ui-tests.md +++ b/docs/ui-tests.md @@ -10,6 +10,20 @@ Currently the test are covering a small set of application flows: - Self verification via emoji - Self verification via passphrase + + +* [Prerequisites:](#prerequisites:) +* [Run the tests](#run-the-tests) + * [From the source code](#from-the-source-code) + * [From command line](#from-command-line) +* [Recipes](#recipes) + * [Wait for initial sync](#wait-for-initial-sync) + * [Accessing current activity](#accessing-current-activity) + * [Interact with other session](#interact-with-other-session) + * [Contributing to the UiAllScreensSanityTest](#contributing-to-the-uiallscreenssanitytest) + + + ## Prerequisites: Out of the box, the tests use one of the homeservers (located at http://localhost:8080) of the "Demo Federation of Homeservers" (https://github.com/matrix-org/synapse#running-a-demo-federation-of-synapses). diff --git a/docs/unifiedpush.md b/docs/unifiedpush.md new file mode 100644 index 0000000000..2851644e66 --- /dev/null +++ b/docs/unifiedpush.md @@ -0,0 +1,58 @@ +# UnifiedPush + + + +* [Introduction](#introduction) +* [Configuration in Element-Android and their forks](#configuration-in-element-android-and-their-forks) + * [Enabling and disabling the feature](#enabling-and-disabling-the-feature) + * [Override the configuration at runtime](#override-the-configuration-at-runtime) + * [Enabling the feature](#enabling-the-feature) + * [Disabling the feature](#disabling-the-feature) + * [Useful links](#useful-links) + + + +## Introduction + +The recently started UnifiedPush project is an Android protocol and library for apps to be able to receive distributor-agnostic push notifications. + +The *F-Droid* and *Gplay* flavors of Element Android support UnifiedPush, so the user can use any distributor installed on their devices. This would make it possible to have push notifications without depending on Google services or libraries. Currently, the main distributors are [ntfy](https://ntfy.sh) which does not require any setup (like manual registration) to use the public server and [NextPush](https://github.com/UP-NextPush/android), available as a nextcloud application. + +The *Gplay* variant uses a UnifiedPush library which basically embed a FCM distributor built into the application (so a user doesn't need to do anything other than install the app to get FCM notifications). This variant uses Google Services to receive notifications if the user has not installed any distributor. + +The *F-Droid* variant does not use this library to avoid any proprietary blob. It will use a polling service if the user has not installed any distributor. + +In all cases, if there are other distributors available, the user will have to opt-in to one of them in the preferences. + +## Configuration in Element-Android and their forks + +### Enabling and disabling the feature + +Allowing the user to use an alternative distributor can be changed in [Config](../vector-config/src/main/java/im/vector/app/config/Config.kt). The flag is named `ALLOW_EXTERNAL_UNIFIED_PUSH_DISTRIBUTORS`. Default value is `true`. + +#### Override the configuration at runtime + +On debug version, it is possible to override this configuration at runtime, using the `Feature` screen. The Feature is named `Allow external UnifiedPush distributors`. + +#### Enabling the feature + +This is the default behavior of Element Android. + +If `ALLOW_EXTERNAL_UNIFIED_PUSH_DISTRIBUTORS` is set to true, it allows any available external UnifiedPush distributor to be chosen by the user. +- For Gplay variant it means that FCM will be used by default, but user can choose another UnifiedPush distributor; +- For F-Droid variant, it means that background polling will be used by default, but user can choose another UnifiedPush distributor. +- On the UI, the setting to choose an alternative distributor will be visible to the user, and some tests in the notification troubleshoot screen will shown. +- For F-Droid, if the user has chosen a distributor, the settings to configure the background polling will be hidden. + +#### Disabling the feature + +If `ALLOW_EXTERNAL_UNIFIED_PUSH_DISTRIBUTORS` is set to false, it prevents the usage of external UnifiedPush distributors. +- For Gplay variant it means that only FCM will be used; +- For F-Droid variant, it means that only background polling will be used. +- On the UI, the setting to choose an alternative distributor will be hidden to the user, and some tests in the notification troubleshoot screen will be hidden. + +### Useful links + +- UnifiedPush official website: [https://unifiedpush.org/](https://unifiedpush.org/) +- List of available distributors can be retrieved here: [https://unifiedpush.org/users/distributors/](https://unifiedpush.org/users/distributors/) +- UnifiedPush project discussion can occurs here: [#unifiedpush:matrix.org](https://matrix.to/#/#unifiedpush:matrix.org) diff --git a/fastlane/README.md b/fastlane/README.md index 7fea7afdd5..dc33f422d6 100644 --- a/fastlane/README.md +++ b/fastlane/README.md @@ -1,64 +1,49 @@ fastlane documentation ----- - +================ # Installation Make sure you have the latest version of the Xcode command line tools installed: -```sh +``` xcode-select --install ``` -For _fastlane_ installation instructions, see [Installing _fastlane_](https://docs.fastlane.tools/#installing-fastlane) +Install _fastlane_ using +``` +[sudo] gem install fastlane -NV +``` +or alternatively using `brew install fastlane` # Available Actions - ## Android - ### android test - -```sh -[bundle exec] fastlane android test ``` - +fastlane android test +``` Runs all the tests - ### android beta - -```sh -[bundle exec] fastlane android beta ``` - +fastlane android beta +``` Submit a new Beta Build to Crashlytics Beta - ### android deploy - -```sh -[bundle exec] fastlane android deploy ``` - +fastlane android deploy +``` Deploy a new version to the Google Play - ### android deployMeta - -```sh -[bundle exec] fastlane android deployMeta ``` - +fastlane android deployMeta +``` Deploy Google Play metadata - ### android getVersionCode - -```sh -[bundle exec] fastlane android getVersionCode ``` - +fastlane android getVersionCode +``` Get version code ---- This README.md is auto-generated and will be re-generated every time [_fastlane_](https://fastlane.tools) is run. - -More information about _fastlane_ can be found on [fastlane.tools](https://fastlane.tools). - -The documentation of _fastlane_ can be found on [docs.fastlane.tools](https://docs.fastlane.tools). +More information about fastlane can be found on [fastlane.tools](https://fastlane.tools). +The documentation of fastlane can be found on [docs.fastlane.tools](https://docs.fastlane.tools). diff --git a/fastlane/metadata/android/cs-CZ/changelogs/40104140.txt b/fastlane/metadata/android/cs-CZ/changelogs/40104140.txt new file mode 100644 index 0000000000..d369efa9f2 --- /dev/null +++ b/fastlane/metadata/android/cs-CZ/changelogs/40104140.txt @@ -0,0 +1,2 @@ +Hlavní změny v této verzi: Zlepšení správy ignorovaných uživatelů. 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/40104160.txt b/fastlane/metadata/android/cs-CZ/changelogs/40104160.txt new file mode 100644 index 0000000000..922d4dc76b --- /dev/null +++ b/fastlane/metadata/android/cs-CZ/changelogs/40104160.txt @@ -0,0 +1,2 @@ +Hlavní změny v této verzi: Vylepšena správa šifrovaných zpráv. 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/40104180.txt b/fastlane/metadata/android/en-US/changelogs/40104180.txt new file mode 100644 index 0000000000..61db61727a --- /dev/null +++ b/fastlane/metadata/android/en-US/changelogs/40104180.txt @@ -0,0 +1,2 @@ +Main changes in this version: Various bug fixes and stability improvements. +Full changelog: https://github.com/vector-im/element-android/releases diff --git a/fastlane/metadata/android/en-US/changelogs/40104190.txt b/fastlane/metadata/android/en-US/changelogs/40104190.txt new file mode 100644 index 0000000000..61db61727a --- /dev/null +++ b/fastlane/metadata/android/en-US/changelogs/40104190.txt @@ -0,0 +1,2 @@ +Main changes in this version: Various bug fixes and stability improvements. +Full changelog: https://github.com/vector-im/element-android/releases diff --git a/fastlane/metadata/android/en-US/changelogs/40104200.txt b/fastlane/metadata/android/en-US/changelogs/40104200.txt new file mode 100644 index 0000000000..61db61727a --- /dev/null +++ b/fastlane/metadata/android/en-US/changelogs/40104200.txt @@ -0,0 +1,2 @@ +Main changes in this version: Various bug fixes and stability improvements. +Full changelog: https://github.com/vector-im/element-android/releases diff --git a/fastlane/metadata/android/en-US/changelogs/40104220.txt b/fastlane/metadata/android/en-US/changelogs/40104220.txt new file mode 100644 index 0000000000..61db61727a --- /dev/null +++ b/fastlane/metadata/android/en-US/changelogs/40104220.txt @@ -0,0 +1,2 @@ +Main changes in this version: Various bug fixes and stability improvements. +Full changelog: https://github.com/vector-im/element-android/releases diff --git a/fastlane/metadata/android/en-US/changelogs/40104230.txt b/fastlane/metadata/android/en-US/changelogs/40104230.txt new file mode 100644 index 0000000000..61db61727a --- /dev/null +++ b/fastlane/metadata/android/en-US/changelogs/40104230.txt @@ -0,0 +1,2 @@ +Main changes in this version: Various bug fixes and stability improvements. +Full changelog: https://github.com/vector-im/element-android/releases diff --git a/fastlane/metadata/android/en-US/changelogs/40104240.txt b/fastlane/metadata/android/en-US/changelogs/40104240.txt new file mode 100644 index 0000000000..61db61727a --- /dev/null +++ b/fastlane/metadata/android/en-US/changelogs/40104240.txt @@ -0,0 +1,2 @@ +Main changes in this version: Various bug fixes and stability improvements. +Full changelog: https://github.com/vector-im/element-android/releases diff --git a/fastlane/metadata/android/en-US/changelogs/40104250.txt b/fastlane/metadata/android/en-US/changelogs/40104250.txt new file mode 100644 index 0000000000..61db61727a --- /dev/null +++ b/fastlane/metadata/android/en-US/changelogs/40104250.txt @@ -0,0 +1,2 @@ +Main changes in this version: Various bug fixes and stability improvements. +Full changelog: https://github.com/vector-im/element-android/releases diff --git a/fastlane/metadata/android/es-ES/changelogs/40104140.txt b/fastlane/metadata/android/es-ES/changelogs/40104140.txt new file mode 100644 index 0000000000..595cd6b3d4 --- /dev/null +++ b/fastlane/metadata/android/es-ES/changelogs/40104140.txt @@ -0,0 +1,2 @@ +Cambios principales en esta versión: Mejoras en la administración de usuarios ignorados. 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/et/changelogs/40104140.txt b/fastlane/metadata/android/et/changelogs/40104140.txt new file mode 100644 index 0000000000..9000616f38 --- /dev/null +++ b/fastlane/metadata/android/et/changelogs/40104140.txt @@ -0,0 +1,2 @@ +Põhilised muutused selles versioonis: eiratud kasutajate parem haldus 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/40104160.txt b/fastlane/metadata/android/et/changelogs/40104160.txt new file mode 100644 index 0000000000..aa5a341dd3 --- /dev/null +++ b/fastlane/metadata/android/et/changelogs/40104160.txt @@ -0,0 +1,2 @@ +Põhilised muutused selles versioonis: krüptitud sõnumite parem haldus, lisaks pisiparandused ja stabiilsust parandavad kohendused. +Kogu ingliskeelne muudatuste logi: https://github.com/vector-im/element-android/releases diff --git a/fastlane/metadata/android/fa/changelogs/40104140.txt b/fastlane/metadata/android/fa/changelogs/40104140.txt new file mode 100644 index 0000000000..cf5d7bc6ac --- /dev/null +++ b/fastlane/metadata/android/fa/changelogs/40104140.txt @@ -0,0 +1,2 @@ +تغییرات عمده در این نگارش: مدیریت بهبودیافتهٔ کاربران چشم‌پوشیده. رفع اشکال‌های مختلف و بهبودهای پایداری. +گزارش دگرگونی کامل: https://github.com/vector-im/element-android/releases diff --git a/fastlane/metadata/android/fa/changelogs/40104160.txt b/fastlane/metadata/android/fa/changelogs/40104160.txt new file mode 100644 index 0000000000..1687ba2419 --- /dev/null +++ b/fastlane/metadata/android/fa/changelogs/40104160.txt @@ -0,0 +1,2 @@ +تغییرات عمده در این نگارش: مدیریت بهتر پیام‌های رمزگذاشته. رفع اشکال‌های مختلف و بهبودهای پایداری. +گزارش دگرگونی کامل: https://github.com/vector-im/element-android/releases diff --git a/fastlane/metadata/android/fr-FR/changelogs/40104100.txt b/fastlane/metadata/android/fr-FR/changelogs/40104100.txt new file mode 100644 index 0000000000..f2453c5539 --- /dev/null +++ b/fastlane/metadata/android/fr-FR/changelogs/40104100.txt @@ -0,0 +1,2 @@ +Principaux changements pour cette version : Défilement dans les messages vocaux. Plusieurs corrections de bogues et d’améliorations de stabilité. +Intégralité des changements : https://github.com/vector-im/element-android/releases diff --git a/fastlane/metadata/android/fr-FR/changelogs/40104110.txt b/fastlane/metadata/android/fr-FR/changelogs/40104110.txt new file mode 100644 index 0000000000..fe61fd021c --- /dev/null +++ b/fastlane/metadata/android/fr-FR/changelogs/40104110.txt @@ -0,0 +1,2 @@ +Principaux changements pour cette version : Plusieurs corrections de bogues et d’améliorations de stabilité. +Intégralité des changements : https://github.com/vector-im/element-android/releases diff --git a/fastlane/metadata/android/fr-FR/changelogs/40104120.txt b/fastlane/metadata/android/fr-FR/changelogs/40104120.txt new file mode 100644 index 0000000000..accd82fe72 --- /dev/null +++ b/fastlane/metadata/android/fr-FR/changelogs/40104120.txt @@ -0,0 +1,2 @@ +Principaux changements pour cette version : Les utilisateurs peuvent apparaître hors-ligne. Ajout d’un lecteur pour les pièces jointes audio +Intégralité des changements : https://github.com/vector-im/element-android/releases diff --git a/fastlane/metadata/android/fr-FR/changelogs/40104130.txt b/fastlane/metadata/android/fr-FR/changelogs/40104130.txt new file mode 100644 index 0000000000..accd82fe72 --- /dev/null +++ b/fastlane/metadata/android/fr-FR/changelogs/40104130.txt @@ -0,0 +1,2 @@ +Principaux changements pour cette version : Les utilisateurs peuvent apparaître hors-ligne. Ajout d’un lecteur pour les pièces jointes audio +Intégralité des changements : https://github.com/vector-im/element-android/releases diff --git a/fastlane/metadata/android/fr-FR/changelogs/40104140.txt b/fastlane/metadata/android/fr-FR/changelogs/40104140.txt new file mode 100644 index 0000000000..087d5bc1c8 --- /dev/null +++ b/fastlane/metadata/android/fr-FR/changelogs/40104140.txt @@ -0,0 +1,2 @@ +Principaux changements pour cette version : Amélioration de la gestion des utilisateurs ignorés. Plusieurs corrections de bogues et d’améliorations de stabilité. +Intégralité des changements : https://github.com/vector-im/element-android/releases diff --git a/fastlane/metadata/android/fr-FR/changelogs/40104160.txt b/fastlane/metadata/android/fr-FR/changelogs/40104160.txt new file mode 100644 index 0000000000..6a19c6ea39 --- /dev/null +++ b/fastlane/metadata/android/fr-FR/changelogs/40104160.txt @@ -0,0 +1,2 @@ +Principaux changements pour cette version : Meilleure gestion des messages chiffrés. Plusieurs corrections de bogues et d’améliorations de stabilité. +Intégralité des changements : https://github.com/vector-im/element-android/releases diff --git a/fastlane/metadata/android/id/changelogs/40104140.txt b/fastlane/metadata/android/id/changelogs/40104140.txt new file mode 100644 index 0000000000..174e8dcded --- /dev/null +++ b/fastlane/metadata/android/id/changelogs/40104140.txt @@ -0,0 +1,2 @@ +Perubahan utama dalam versi ini: Tingkatkan pengelolaan pengguna yang diabaikan. Beberapa perbaikan kutu dan stabilitas. +Catatan perubahan lanjutan: https://github.com/vector-im/element-android/releases diff --git a/fastlane/metadata/android/id/changelogs/40104160.txt b/fastlane/metadata/android/id/changelogs/40104160.txt new file mode 100644 index 0000000000..8f248ffed4 --- /dev/null +++ b/fastlane/metadata/android/id/changelogs/40104160.txt @@ -0,0 +1,2 @@ +Perubahan utama dalam versi ini: Pengelolaan pesan terenkripsi lebih baik. 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/40104140.txt b/fastlane/metadata/android/it-IT/changelogs/40104140.txt new file mode 100644 index 0000000000..ae367e72d0 --- /dev/null +++ b/fastlane/metadata/android/it-IT/changelogs/40104140.txt @@ -0,0 +1,2 @@ +Modifiche principali in questa versione: migliorata la gestione degli utenti ignorati. 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/40104160.txt b/fastlane/metadata/android/it-IT/changelogs/40104160.txt new file mode 100644 index 0000000000..943a42de99 --- /dev/null +++ b/fastlane/metadata/android/it-IT/changelogs/40104160.txt @@ -0,0 +1,2 @@ +Modifiche principali in questa versione: migliorata la gestione dei messaggi cifrati. Varie correzioni e miglioramenti della stabilità. +Cronologia completa: https://github.com/vector-im/element-android/releases diff --git a/fastlane/metadata/android/iw-IL/title.txt b/fastlane/metadata/android/iw-IL/title.txt index d8849a5023..56ae517696 100644 --- a/fastlane/metadata/android/iw-IL/title.txt +++ b/fastlane/metadata/android/iw-IL/title.txt @@ -1 +1 @@ -אלמנט (בעבר Riot.im) +אלמנט - התכתבות מאובטחת diff --git a/fastlane/metadata/android/lo-LA/changelogs/40104140.txt b/fastlane/metadata/android/lo-LA/changelogs/40104140.txt new file mode 100644 index 0000000000..e8c8d84031 --- /dev/null +++ b/fastlane/metadata/android/lo-LA/changelogs/40104140.txt @@ -0,0 +1,2 @@ +ການປ່ຽນແປງຫຼັກໃນສະບັບນີ້: ປັບປຸງການບໍລິຫານການລະເວັ້ນຜູ້ໃຊ້. ປັບປຸງບັກ ແລະຄວາມສະຖຽນ. +ບັນທຶກການປ່ຽນແປງສະບັບເຕັມ: https://github.com/vector-im/element-android/releases diff --git a/fastlane/metadata/android/pl-PL/changelogs/40101060.txt b/fastlane/metadata/android/pl-PL/changelogs/40101060.txt new file mode 100644 index 0000000000..17af495632 --- /dev/null +++ b/fastlane/metadata/android/pl-PL/changelogs/40101060.txt @@ -0,0 +1,2 @@ +Główne zmiany w tej wesji: poprawki dla wesji 1.1.5 +Pełna lista zmian: https://github.com/vector-im/element-android/releases/tag/v1.1.6 diff --git a/fastlane/metadata/android/pl-PL/changelogs/40101070.txt b/fastlane/metadata/android/pl-PL/changelogs/40101070.txt new file mode 100644 index 0000000000..2959ba88e0 --- /dev/null +++ b/fastlane/metadata/android/pl-PL/changelogs/40101070.txt @@ -0,0 +1,2 @@ +Główne zmiany w tej wersji: Eksperymentalne wsparcie dla przestrzeni, Kompresowanie video przed wysłaniem. +Pełna lista zmian: https://github.com/vector-im/element-android/releases/tag/v1.1.7 diff --git a/fastlane/metadata/android/pl-PL/changelogs/40101080.txt b/fastlane/metadata/android/pl-PL/changelogs/40101080.txt new file mode 100644 index 0000000000..95f77a0f25 --- /dev/null +++ b/fastlane/metadata/android/pl-PL/changelogs/40101080.txt @@ -0,0 +1,2 @@ +Główne zmiany w tej wersji: Poprawki w przestrzeniach +Pełna lista zmian https://github.com/vector-im/element-android/releases/tag/v1.1.8 diff --git a/fastlane/metadata/android/pl-PL/changelogs/40101090.txt b/fastlane/metadata/android/pl-PL/changelogs/40101090.txt new file mode 100644 index 0000000000..0bc111fa70 --- /dev/null +++ b/fastlane/metadata/android/pl-PL/changelogs/40101090.txt @@ -0,0 +1,2 @@ +Główne zmiany w tej wersji: Dodano wsparcie dla sieci gitter.im . +Pełna lista zmian: https://github.com/vector-im/element-android/releases/tag/v1.1.9 diff --git a/fastlane/metadata/android/pl-PL/changelogs/40101100.txt b/fastlane/metadata/android/pl-PL/changelogs/40101100.txt new file mode 100644 index 0000000000..2eace7771d --- /dev/null +++ b/fastlane/metadata/android/pl-PL/changelogs/40101100.txt @@ -0,0 +1,2 @@ +Główne zmiany w tej wersji: Nowe funkcje dla przestrzeni i aktualizacja motywu i stylu. +Pełna lista zmian: https://github.com/vector-im/element-android/releases/tag/v1.1.10 diff --git a/fastlane/metadata/android/pl-PL/changelogs/40101110.txt b/fastlane/metadata/android/pl-PL/changelogs/40101110.txt new file mode 100644 index 0000000000..5afc3ca5a7 --- /dev/null +++ b/fastlane/metadata/android/pl-PL/changelogs/40101110.txt @@ -0,0 +1,2 @@ +Główne zmiany w tej wersji: Dodano nowe funkcje do przestrzeni i zaktualizowano motyw i styl aplikacji. (poprawki błędóœ dla wesji 1.1.10) +Pełna lista zmian: https://github.com/vector-im/element-android/releases/tag/v1.1.11 diff --git a/fastlane/metadata/android/pl-PL/changelogs/40101120.txt b/fastlane/metadata/android/pl-PL/changelogs/40101120.txt new file mode 100644 index 0000000000..6a62f1c6a9 --- /dev/null +++ b/fastlane/metadata/android/pl-PL/changelogs/40101120.txt @@ -0,0 +1,2 @@ +Główne zmiany w tej wersji: aktualizacja motywu i stylu oraz naprawa awarii po rozmowie wideo +Pełna lista zmian: https://github.com/vector-im/element-android/releases/tag/v1.1.12 diff --git a/fastlane/metadata/android/pl-PL/changelogs/40101130.txt b/fastlane/metadata/android/pl-PL/changelogs/40101130.txt new file mode 100644 index 0000000000..ec8d488eb8 --- /dev/null +++ b/fastlane/metadata/android/pl-PL/changelogs/40101130.txt @@ -0,0 +1,2 @@ +Główne zmiany w tej wersji: głównie aktualizacja stabilności i poprawki błędów. +Pełna lista zmian: https://github.com/vector-im/element-android/releases/tag/v1.1.13 diff --git a/fastlane/metadata/android/pl-PL/changelogs/40101140.txt b/fastlane/metadata/android/pl-PL/changelogs/40101140.txt new file mode 100644 index 0000000000..c4c102da4a --- /dev/null +++ b/fastlane/metadata/android/pl-PL/changelogs/40101140.txt @@ -0,0 +1,2 @@ +Główne zmiany w tej wersji: naprawienie problemu z zaszyfrowanymi wiadomościami. +Pełna lista zmian: https://github.com/vector-im/element-android/releases/tag/v1.1.14 diff --git a/fastlane/metadata/android/pl-PL/changelogs/40101150.txt b/fastlane/metadata/android/pl-PL/changelogs/40101150.txt new file mode 100644 index 0000000000..2eb1a3f018 --- /dev/null +++ b/fastlane/metadata/android/pl-PL/changelogs/40101150.txt @@ -0,0 +1,2 @@ +Główne zmiany w tej wersji: implementacja wiadomości głosowych w ustawieniach laboratorium. +Pełna lista zmian: https://github.com/vector-im/element-android/releases/tag/v1.1.15 diff --git a/fastlane/metadata/android/pl-PL/changelogs/40101160.txt b/fastlane/metadata/android/pl-PL/changelogs/40101160.txt new file mode 100644 index 0000000000..682da8be76 --- /dev/null +++ b/fastlane/metadata/android/pl-PL/changelogs/40101160.txt @@ -0,0 +1,2 @@ +Główne zmiany w tej wersji: Naprawiono błąd podczas wysyłania zaszyfrowanej wiadomości, jeśli ktoś w pokoju się wyloguje. +Pełna lista zmian: https://github.com/vector-im/element-android/releases/tag/v1.1.16 diff --git a/fastlane/metadata/android/pl-PL/changelogs/40102000.txt b/fastlane/metadata/android/pl-PL/changelogs/40102000.txt new file mode 100644 index 0000000000..cdae0a4ba7 --- /dev/null +++ b/fastlane/metadata/android/pl-PL/changelogs/40102000.txt @@ -0,0 +1,2 @@ +Główne zmiany w tej wersji: Wiadomość głosowa jest domyślnie włączona. +Pełna lista zmian: https://github.com/vector-im/element-android/releases/tag/v1.2.0 diff --git a/fastlane/metadata/android/pl-PL/changelogs/40102010.txt b/fastlane/metadata/android/pl-PL/changelogs/40102010.txt new file mode 100644 index 0000000000..0a825e8672 --- /dev/null +++ b/fastlane/metadata/android/pl-PL/changelogs/40102010.txt @@ -0,0 +1,2 @@ +Główne zmiany w tej wersji: Wiele ulepszeń w VoIP i Przestrzeniach (nadal w wersji beta). +Pełna lista zmian: https://github.com/vector-im/element-android/releases/tag/v1.2.1 diff --git a/fastlane/metadata/android/pl-PL/changelogs/40103000.txt b/fastlane/metadata/android/pl-PL/changelogs/40103000.txt new file mode 100644 index 0000000000..8b408ced72 --- /dev/null +++ b/fastlane/metadata/android/pl-PL/changelogs/40103000.txt @@ -0,0 +1,2 @@ +Główne zmiany w tej wersji: Organizuj swoje pokoje za pomocą Przestrzeni! +Pełna lista zmian: https://github.com/vector-im/element-android/releases/tag/v1.3.0 diff --git a/fastlane/metadata/android/pl-PL/changelogs/40103010.txt b/fastlane/metadata/android/pl-PL/changelogs/40103010.txt new file mode 100644 index 0000000000..0a49e7fa68 --- /dev/null +++ b/fastlane/metadata/android/pl-PL/changelogs/40103010.txt @@ -0,0 +1,2 @@ +Główne zmiany w tej wersji: Organizuj swoje pokoje za pomocą Przestrzeni! Wersja 1.3.1 naprawia awarię, która może wystąpić w wersji 1.3.0. +Pełna lista zmian: https://github.com/vector-im/element-android/releases/tag/v1.3.1 diff --git a/fastlane/metadata/android/pl-PL/changelogs/40103020.txt b/fastlane/metadata/android/pl-PL/changelogs/40103020.txt new file mode 100644 index 0000000000..3e37f64b76 --- /dev/null +++ b/fastlane/metadata/android/pl-PL/changelogs/40103020.txt @@ -0,0 +1,2 @@ +Główne zmiany w tej wersji: Dodano obsługę Android Auto. Wiele poprawek błędów! +Pełna lista zmian: https://github.com/vector-im/element-android/releases/tag/v1.3.2 diff --git a/fastlane/metadata/android/pl-PL/changelogs/40103030.txt b/fastlane/metadata/android/pl-PL/changelogs/40103030.txt new file mode 100644 index 0000000000..8f80d95b5a --- /dev/null +++ b/fastlane/metadata/android/pl-PL/changelogs/40103030.txt @@ -0,0 +1,2 @@ +Główne zmiany w tej wersji: Uwidocznij politykę(-i) serwera tożsamości w ustawieniach. Tymczasowo usunięto obsługę Androida Auto. +Pełna lista zmian: https://github.com/vector-im/element-android/releases/tag/v1.3.3 diff --git a/fastlane/metadata/android/pl-PL/changelogs/40103040.txt b/fastlane/metadata/android/pl-PL/changelogs/40103040.txt new file mode 100644 index 0000000000..13458a7b2d --- /dev/null +++ b/fastlane/metadata/android/pl-PL/changelogs/40103040.txt @@ -0,0 +1,2 @@ +Główne zmiany w tej wersji: Dodanie obsługi obecności, dla pokoju wiadomości bezpośrednich (uwaga: obecność jest wyłączona na matrix.org). Dodano ponownie obsługę Androida Auto. +Pełna lista zmian: https://github.com/vector-im/element-android/releases/tag/v1.3.4 diff --git a/fastlane/metadata/android/pl-PL/changelogs/40104140.txt b/fastlane/metadata/android/pl-PL/changelogs/40104140.txt new file mode 100644 index 0000000000..84ab57a2ac --- /dev/null +++ b/fastlane/metadata/android/pl-PL/changelogs/40104140.txt @@ -0,0 +1,2 @@ +Główne zmiany w tej wersji: Poprawa zarządzania ignorowanymi użytkownikami. Różne poprawki błędów i ulepszenia stabilności. +Pełna lista zmian: https://github.com/vector-im/element-android/releases diff --git a/fastlane/metadata/android/pl-PL/changelogs/40104160.txt b/fastlane/metadata/android/pl-PL/changelogs/40104160.txt new file mode 100644 index 0000000000..104ed45352 --- /dev/null +++ b/fastlane/metadata/android/pl-PL/changelogs/40104160.txt @@ -0,0 +1,2 @@ +Główne zmiany w tej wersji: Lepsze zarządzanie zaszyfrowanymi wiadomościami. , Poprawki błędów i stabilności. +Pełna lista zmian: https://github.com/vector-im/element-android/releases diff --git a/fastlane/metadata/android/pt-BR/changelogs/40104140.txt b/fastlane/metadata/android/pt-BR/changelogs/40104140.txt new file mode 100644 index 0000000000..ed1a53c910 --- /dev/null +++ b/fastlane/metadata/android/pt-BR/changelogs/40104140.txt @@ -0,0 +1,2 @@ +Principais mudanças nesta versão: Melhorar gerenciamento de usuárias(os) ignoradas(os). 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/40104160.txt b/fastlane/metadata/android/pt-BR/changelogs/40104160.txt new file mode 100644 index 0000000000..eec5dca30f --- /dev/null +++ b/fastlane/metadata/android/pt-BR/changelogs/40104160.txt @@ -0,0 +1,2 @@ +Principais mudanças nesta versão: Melhor gerenciamento de mensagens encriptadas. 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/40104140.txt b/fastlane/metadata/android/sk/changelogs/40104140.txt new file mode 100644 index 0000000000..e1c85961a4 --- /dev/null +++ b/fastlane/metadata/android/sk/changelogs/40104140.txt @@ -0,0 +1,2 @@ +Hlavné zmeny v tejto verzii: Zlepšenie správy ignorovaných používateľov. 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/40104160.txt b/fastlane/metadata/android/sk/changelogs/40104160.txt new file mode 100644 index 0000000000..e6e2f45c32 --- /dev/null +++ b/fastlane/metadata/android/sk/changelogs/40104160.txt @@ -0,0 +1,2 @@ +Hlavné zmeny v tejto verzii: Lepšia spravovanie zašifrovaných správ. 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/sv-SE/changelogs/40104140.txt b/fastlane/metadata/android/sv-SE/changelogs/40104140.txt new file mode 100644 index 0000000000..9b58878dfb --- /dev/null +++ b/fastlane/metadata/android/sv-SE/changelogs/40104140.txt @@ -0,0 +1,2 @@ +Huvudsakliga ändringar i den här versionen: Förbättra hantering av ignorerade användare. Diverse buggfixar och stabilitetsförbättringar. +Full ändringslogg: https://github.com/vector-im/element-android/releases diff --git a/fastlane/metadata/android/sv-SE/changelogs/40104160.txt b/fastlane/metadata/android/sv-SE/changelogs/40104160.txt new file mode 100644 index 0000000000..cf7eda6ef8 --- /dev/null +++ b/fastlane/metadata/android/sv-SE/changelogs/40104160.txt @@ -0,0 +1,2 @@ +Huvudsakliga ändringar i den här versionen: Bättre hantering av krypterade meddelanden. Diverse buggfixar och stabilitetsförbättringar. +Full ändringslogg: https://github.com/vector-im/element-android/releases diff --git a/fastlane/metadata/android/ta-IN/changelogs/40104140.txt b/fastlane/metadata/android/ta-IN/changelogs/40104140.txt new file mode 100644 index 0000000000..f3196b57b6 --- /dev/null +++ b/fastlane/metadata/android/ta-IN/changelogs/40104140.txt @@ -0,0 +1,2 @@ +இந்த பதிப்பில் உள்ள முதன்மை மாற்றங்கள்: தவிர்க்கப்பட்ட பயனர்களின் மேலாண்மை மேம்படுத்தப்பட்டுள்ளது. வெவ்வேறு வழுக்களைச் சரிசெய்தல் மற்றும் நிலைப்புத்தன்மையை மேம்படுத்தல். +முழு மாற்ற அறிக்கை: https://github.com/vector-im/element-android/releases diff --git a/fastlane/metadata/android/ta-IN/full_description.txt b/fastlane/metadata/android/ta-IN/full_description.txt new file mode 100644 index 0000000000..9aa693bda2 --- /dev/null +++ b/fastlane/metadata/android/ta-IN/full_description.txt @@ -0,0 +1,42 @@ +Element is both a secure messenger and a productivity team collaboration app that is ideal for group chats while remote working. This chat app uses end-to-end encryption to provide powerful video conferencing, file sharing and voice calls. + +Element இன் தனிச்சிறப்புகளுள் சில: +- மேம்பட்ட இயங்கலை தொடர்பு கருவிகள் +- தொலைநிலையில் உள்ள ஊழியர்களுக்கும், பாதுகாப்பான நிறும கருத்து பரிமாற்றங்களை அனுமதிப்பதற்காக, முழுவதுமாக மறைகுறியாக்கப்பட்ட செய்திகள் +- MATRIX திறந்த மூல கட்டமைப்பை அடிப்படையாக கொண்டு செயல்படும் அதிகாரப்பரவலாக்கப்பட்ட அரட்டை +- செயல்திட்டங்களை மேலாண்மை செய்யும் போது, மறைகுறியாக்கப்பட்ட தரவுடன் கூடிய பாதுகாப்பான கோப்பு பகிரல் +- IP மூலம் குரல் (VoIP) மற்றும் திரை பகிரல் உடன் கூடிய குரல் அரட்டைகள் +- உங்கள் மனம் கவர்ந்த இயங்கலை உடனிணைவு கருவிகள், செயல்திட்ட மேலாண்மை கருவிகள், VoIP சேவைகள் மற்றும் இதர குழு தூதுரை செயலிகள் உடன் கூடிய எளிமையான ஒருமைப்பாடு + +Element is completely different from other messaging and collaboration apps. It operates on Matrix, an open network for secure messaging and decentralized communication. It allows self-hosting to give users maximum ownership and control of their data and messages. + +தனியுரிமை மற்றும் மறைகுறியாக்கப்பட்ட செய்தி அனுப்பல் +தேவையில்லாத விளம்பரங்கள், தரவு சுரண்டல் மற்றும் தகவல் கட்டுப்பாடு போன்றவற்றில் இருந்து Element உங்களை பாதுகாக்கிறது. மேலும், இது முனைக்கு-முனை மறைகுறியாக்கம் மற்றும் குறுக்கு-ஒப்பமிடப்பட்ட சாதன சரிபார்ப்பு ஆகியவற்றின் மூலம் உங்கள் எல்லா தரவுகள், ஒன்றுக்கொன்றான காணொளி மற்றும் குரல் அழைப்புகளை பாதுகாக்கிறது. + +Element gives you control over your privacy while allowing you to communicate securely with யாரோனும் ஒருவருடன் on the Matrix network, or other business collaboration tools by integrating with apps such as Slack. + +Element can be self-hosted +To allow more control of your sensitive data and conversations, Element can be self-hosted or you can choose any Matrix-based host - the standard for open source, decentralized communication. Element gives you privacy, security compliance and integration flexibility. + +உங்கள் தரவைச் சொந்தமாக்கிக் கொள்ளுங்கள் +தரவுகள் மற்றும் செய்திகளை எங்கு சேமித்து வைக்க வேண்டும் என்பதை நீங்கள் முடிவு செய்கிறீர்கள். இதன்மூலம், தரவு சுரண்டல் மற்றும் மூன்றாம் தரப்பினர் அனுகல் ஆகிய இடர்களை தவிர்க்கலாம். + +Element வெவ்வேறு வகையில் கட்டுப்பாட்டை உங்களிடம் அளிக்கிறது: +1. Get a free account on the matrix.org public server hosted by the Matrix developers, or choose from thousands of public servers hosted by volunteers +2. Self-host your account by running a server on your own IT infrastructure +3. Sign up for an account on a custom server by simply subscribing to the Element Matrix Services hosting platform + +திறந்த செய்தி அனுப்பல் மற்றும் ஒருமைப்பாடு +You can chat with anyone on the Matrix network, whether they’re using Element, another Matrix app or even if they are using a different messaging app. + +மிகவும் பாதுகாப்பானது +உண்மையான முனைக்கு-முனை மறைகுறியாக்கம் (உரையாடலில் உள்ளவர்கள் மட்டுமே மறைகுறியாக்கத்தை நீக்கி செய்தியை காண இயலும்) மற்றும் குறுக்கு-ஒப்பமிடப்பட்ட சாதன சரிபார்ப்பு. + +முழுமையான தொடர்பு மற்றும் ஒருமைப்பாடு +செய்தி அனுப்பல், காணொளி மற்றும் குரல் அழைப்புகளை, கோப்பு பகிரல், திரை பகிரல் மற்றும் ஒருமைப்பாடுகள், இயலிகள் மற்றும் நிரல் பலகைகளின் மொத்த கொத்து. அறைகள், குழுக்களை உருவாக்கி, அவர்களுடன் உரையாடி, வேலையை எளிமையாக்கவும். + +எங்கு விட்டு சென்றீர்களோ அதிலிருந்த துவங்கவும் +Stay in touch wherever you are with fully synchronised message history across all your devices and on the web at https://app.element.io + +திறந்த மூலம் +Element Android ஒரு திறந்த மூல செயல் திட்டமாகும். இது GitHub இல் தொகுத்து வழங்கப்பட்டுள்ளது. வழுக்கள் ஏதேனும் கண்டறிந்தால் மற்றும்/அல்லது இதன் வளர்ச்சிக்கு பங்களிக்க விரும்பினால், https://github.com/vector-im/element-android என்னும் தளத்திற்கு வருகை தரவும். diff --git a/fastlane/metadata/android/ta-IN/short_description.txt b/fastlane/metadata/android/ta-IN/short_description.txt new file mode 100644 index 0000000000..9c7afb2a37 --- /dev/null +++ b/fastlane/metadata/android/ta-IN/short_description.txt @@ -0,0 +1 @@ +மறைகுறியாக்கப்பட்ட செய்தி அனுப்பல், குழு அரட்டை மற்றும் காணொளி அழைப்புகள் diff --git a/fastlane/metadata/android/ta-IN/title.txt b/fastlane/metadata/android/ta-IN/title.txt new file mode 100644 index 0000000000..ecb9a01c06 --- /dev/null +++ b/fastlane/metadata/android/ta-IN/title.txt @@ -0,0 +1 @@ +Element - பாதுகாப்பான தூதுரை சேவை diff --git a/fastlane/metadata/android/uk/changelogs/40104140.txt b/fastlane/metadata/android/uk/changelogs/40104140.txt new file mode 100644 index 0000000000..293ad117e4 --- /dev/null +++ b/fastlane/metadata/android/uk/changelogs/40104140.txt @@ -0,0 +1,2 @@ +Основні зміни у цій версії: Удосконалено керування нехтуваними користувачами. Різні виправлення помилок та поліпшення стабільності. +Вичерпний журнал змін: https://github.com/vector-im/element-android/releases diff --git a/fastlane/metadata/android/uk/changelogs/40104160.txt b/fastlane/metadata/android/uk/changelogs/40104160.txt new file mode 100644 index 0000000000..33ad65f75c --- /dev/null +++ b/fastlane/metadata/android/uk/changelogs/40104160.txt @@ -0,0 +1,2 @@ +Основні зміни в цій версії: Поліпшено керування зашифрованими повідомленнями. Усунуто різні вади й поліпшено стабільність. +Вичерпний перелік змін: https://github.com/vector-im/element-android/releases diff --git a/fastlane/metadata/android/zh-TW/changelogs/40104140.txt b/fastlane/metadata/android/zh-TW/changelogs/40104140.txt new file mode 100644 index 0000000000..ff830dab7c --- /dev/null +++ b/fastlane/metadata/android/zh-TW/changelogs/40104140.txt @@ -0,0 +1,2 @@ +此版本中的主要變動:改善被忽略使用者的管理。多個臭蟲修復與穩定性改善。 +完整的變更紀錄:https://github.com/vector-im/element-android/releases diff --git a/fastlane/metadata/android/zh-TW/changelogs/40104160.txt b/fastlane/metadata/android/zh-TW/changelogs/40104160.txt new file mode 100644 index 0000000000..0e64d36868 --- /dev/null +++ b/fastlane/metadata/android/zh-TW/changelogs/40104160.txt @@ -0,0 +1,2 @@ +此版本中的主要變動:對於被加密的訊息有更好的管理方式。多個臭蟲修復與穩定性改善。 +完整的變更紀錄:https://github.com/vector-im/element-android/releases diff --git a/library/attachment-viewer/build.gradle b/library/attachment-viewer/build.gradle index 048710f62c..8bbafd3387 100644 --- a/library/attachment-viewer/build.gradle +++ b/library/attachment-viewer/build.gradle @@ -55,5 +55,6 @@ dependencies { implementation libs.androidx.appCompat implementation libs.androidx.recyclerview - implementation libs.google.material -} \ No newline at end of file + api libs.androidx.viewpager2 + implementation libs.androidx.transition +} diff --git a/library/attachment-viewer/src/main/java/im/vector/lib/attachmentviewer/AttachmentViewerActivity.kt b/library/attachment-viewer/src/main/java/im/vector/lib/attachmentviewer/AttachmentViewerActivity.kt index 21af114c26..764cf8419a 100644 --- a/library/attachment-viewer/src/main/java/im/vector/lib/attachmentviewer/AttachmentViewerActivity.kt +++ b/library/attachment-viewer/src/main/java/im/vector/lib/attachmentviewer/AttachmentViewerActivity.kt @@ -271,7 +271,7 @@ abstract class AttachmentViewerActivity : AppCompatActivity(), AttachmentEventLi directionDetector.handleTouchEvent(event) return when (swipeDirection) { - SwipeDirection.Up, SwipeDirection.Down -> { + SwipeDirection.Up, SwipeDirection.Down -> { if (isSwipeToDismissAllowed && !wasScaled && isImagePagerIdle) { swipeDismissHandler.onTouch(views.rootContainer, event) } else true @@ -279,7 +279,7 @@ abstract class AttachmentViewerActivity : AppCompatActivity(), AttachmentEventLi SwipeDirection.Left, SwipeDirection.Right -> { views.attachmentPager.dispatchTouchEvent(event) } - else -> true + else -> true } } diff --git a/library/attachment-viewer/src/main/java/im/vector/lib/attachmentviewer/AttachmentsAdapter.kt b/library/attachment-viewer/src/main/java/im/vector/lib/attachmentviewer/AttachmentsAdapter.kt index 4805a1186b..77ddb27c63 100644 --- a/library/attachment-viewer/src/main/java/im/vector/lib/attachmentviewer/AttachmentsAdapter.kt +++ b/library/attachment-viewer/src/main/java/im/vector/lib/attachmentviewer/AttachmentsAdapter.kt @@ -42,18 +42,18 @@ class AttachmentsAdapter : RecyclerView.Adapter() { val inflater = LayoutInflater.from(parent.context) val itemView = inflater.inflate(viewType, parent, false) return when (viewType) { - R.layout.item_image_attachment -> ZoomableImageViewHolder(itemView) + R.layout.item_image_attachment -> ZoomableImageViewHolder(itemView) R.layout.item_animated_image_attachment -> AnimatedImageViewHolder(itemView) - R.layout.item_video_attachment -> VideoViewHolder(itemView) - else -> UnsupportedViewHolder(itemView) + R.layout.item_video_attachment -> VideoViewHolder(itemView) + else -> UnsupportedViewHolder(itemView) } } override fun getItemViewType(position: Int): Int { val info = attachmentSourceProvider!!.getAttachmentInfoAt(position) return when (info) { - is AttachmentInfo.Image -> R.layout.item_image_attachment - is AttachmentInfo.Video -> R.layout.item_video_attachment + is AttachmentInfo.Image -> R.layout.item_image_attachment + is AttachmentInfo.Video -> R.layout.item_video_attachment is AttachmentInfo.AnimatedImage -> R.layout.item_animated_image_attachment // is AttachmentInfo.Audio -> TODO() // is AttachmentInfo.File -> TODO() @@ -68,13 +68,13 @@ class AttachmentsAdapter : RecyclerView.Adapter() { attachmentSourceProvider?.getAttachmentInfoAt(position)?.let { holder.bind(it) when (it) { - is AttachmentInfo.Image -> { + is AttachmentInfo.Image -> { attachmentSourceProvider?.loadImage((holder as ZoomableImageViewHolder).target, it) } is AttachmentInfo.AnimatedImage -> { attachmentSourceProvider?.loadImage((holder as AnimatedImageViewHolder).target, it) } - is AttachmentInfo.Video -> { + is AttachmentInfo.Video -> { attachmentSourceProvider?.loadVideo((holder as VideoViewHolder).target, it) } // else -> { diff --git a/library/attachment-viewer/src/main/java/im/vector/lib/attachmentviewer/SwipeDirection.kt b/library/attachment-viewer/src/main/java/im/vector/lib/attachmentviewer/SwipeDirection.kt index 7948f37ae8..7816a5a2cc 100644 --- a/library/attachment-viewer/src/main/java/im/vector/lib/attachmentviewer/SwipeDirection.kt +++ b/library/attachment-viewer/src/main/java/im/vector/lib/attachmentviewer/SwipeDirection.kt @@ -27,12 +27,12 @@ sealed class SwipeDirection { companion object { fun fromAngle(angle: Double): SwipeDirection { return when (angle) { - in 0.0..45.0 -> Right - in 45.0..135.0 -> Up + in 0.0..45.0 -> Right + in 45.0..135.0 -> Up in 135.0..225.0 -> Left in 225.0..315.0 -> Down in 315.0..360.0 -> Right - else -> NotDetected + else -> NotDetected } } } diff --git a/library/attachment-viewer/src/main/java/im/vector/lib/attachmentviewer/SwipeDirectionDetector.kt b/library/attachment-viewer/src/main/java/im/vector/lib/attachmentviewer/SwipeDirectionDetector.kt index 6575248b2d..7b72637c06 100644 --- a/library/attachment-viewer/src/main/java/im/vector/lib/attachmentviewer/SwipeDirectionDetector.kt +++ b/library/attachment-viewer/src/main/java/im/vector/lib/attachmentviewer/SwipeDirectionDetector.kt @@ -33,7 +33,7 @@ class SwipeDirectionDetector( fun handleTouchEvent(event: MotionEvent) { when (event.action) { - MotionEvent.ACTION_DOWN -> { + MotionEvent.ACTION_DOWN -> { startX = event.x startY = event.y } @@ -45,7 +45,7 @@ class SwipeDirectionDetector( startX = startY isDetected = false } - MotionEvent.ACTION_MOVE -> if (!isDetected && getEventDistance(event) > touchSlop) { + MotionEvent.ACTION_MOVE -> if (!isDetected && getEventDistance(event) > touchSlop) { isDetected = true onDirectionDetected(getDirection(startX, startY, event.x, event.y)) } diff --git a/library/attachment-viewer/src/main/java/im/vector/lib/attachmentviewer/SwipeToDismissHandler.kt b/library/attachment-viewer/src/main/java/im/vector/lib/attachmentviewer/SwipeToDismissHandler.kt index ca2c28b498..7a83ee28d4 100644 --- a/library/attachment-viewer/src/main/java/im/vector/lib/attachmentviewer/SwipeToDismissHandler.kt +++ b/library/attachment-viewer/src/main/java/im/vector/lib/attachmentviewer/SwipeToDismissHandler.kt @@ -44,7 +44,7 @@ class SwipeToDismissHandler( @SuppressLint("ClickableViewAccessibility") override fun onTouch(v: View, event: MotionEvent): Boolean { when (event.action) { - MotionEvent.ACTION_DOWN -> { + MotionEvent.ACTION_DOWN -> { if (swipeView.hitRect.contains(event.x.toInt(), event.y.toInt())) { isTracking = true } @@ -58,7 +58,7 @@ class SwipeToDismissHandler( } return true } - MotionEvent.ACTION_MOVE -> { + MotionEvent.ACTION_MOVE -> { if (isTracking) { val translationY = event.y - startY swipeView.translationY = translationY @@ -66,7 +66,7 @@ class SwipeToDismissHandler( } return true } - else -> { + else -> { return false } } @@ -79,8 +79,8 @@ class SwipeToDismissHandler( private fun onTrackingEnd(parentHeight: Int) { val animateTo = when { swipeView.translationY < -translationLimit -> -parentHeight.toFloat() - swipeView.translationY > translationLimit -> parentHeight.toFloat() - else -> 0f + swipeView.translationY > translationLimit -> parentHeight.toFloat() + else -> 0f } if (animateTo != 0f && !shouldAnimateDismiss()) { diff --git a/library/attachment-viewer/src/main/java/im/vector/lib/attachmentviewer/VideoViewHolder.kt b/library/attachment-viewer/src/main/java/im/vector/lib/attachmentviewer/VideoViewHolder.kt index 12213a8786..92d28d26c9 100644 --- a/library/attachment-viewer/src/main/java/im/vector/lib/attachmentviewer/VideoViewHolder.kt +++ b/library/attachment-viewer/src/main/java/im/vector/lib/attachmentviewer/VideoViewHolder.kt @@ -146,7 +146,7 @@ class VideoViewHolder constructor(itemView: View) : wasPaused = true views.videoView.pause() } - is AttachmentCommands.SeekTo -> { + is AttachmentCommands.SeekTo -> { val duration = views.videoView.duration if (duration > 0) { val seekDuration = duration * (commands.percentProgress / 100f) diff --git a/library/core-utils/build.gradle b/library/core-utils/build.gradle index d3afd8d29b..0f7789a2a8 100644 --- a/library/core-utils/build.gradle +++ b/library/core-utils/build.gradle @@ -50,6 +50,5 @@ android { } dependencies { - implementation libs.androidx.appCompat implementation libs.jetbrains.coroutinesAndroid } diff --git a/library/jsonviewer/build.gradle b/library/jsonviewer/build.gradle index 2110747feb..e1a3b0c9ee 100644 --- a/library/jsonviewer/build.gradle +++ b/library/jsonviewer/build.gradle @@ -52,6 +52,7 @@ dependencies { implementation libs.androidx.appCompat implementation libs.androidx.core + implementation libs.androidx.recyclerview implementation libs.airbnb.epoxy kapt libs.airbnb.epoxyProcessor @@ -60,7 +61,6 @@ dependencies { // Span utils implementation 'me.gujun.android:span:1.7' - implementation libs.google.material implementation libs.jetbrains.coroutinesCore implementation libs.jetbrains.coroutinesAndroid diff --git a/library/jsonviewer/src/main/java/org/billcarsonfr/jsonviewer/JSonViewerEpoxyController.kt b/library/jsonviewer/src/main/java/org/billcarsonfr/jsonviewer/JSonViewerEpoxyController.kt index 9f8093f801..24b9f2ec26 100644 --- a/library/jsonviewer/src/main/java/org/billcarsonfr/jsonviewer/JSonViewerEpoxyController.kt +++ b/library/jsonviewer/src/main/java/org/billcarsonfr/jsonviewer/JSonViewerEpoxyController.kt @@ -43,7 +43,7 @@ internal class JSonViewerEpoxyController(private val context: Context) : text(async.error.localizedMessage?.toEpoxyCharSequence()) } } - else -> { + else -> { async.invoke()?.let { buildRec(it, 0, "") } @@ -98,7 +98,7 @@ internal class JSonViewerEpoxyController(private val context: Context) : } } } - is JSonViewerArray -> { + is JSonViewerArray -> { if (model.isExpanded) { open(id, model.key, model.index, depth, false, model) model.items.forEach { @@ -137,7 +137,7 @@ internal class JSonViewerEpoxyController(private val context: Context) : } } } - is JSonViewerLeaf -> { + is JSonViewerLeaf -> { valueItem { id(id) depth(depth) @@ -172,12 +172,12 @@ internal class JSonViewerEpoxyController(private val context: Context) : private fun valueToSpan(leaf: JSonViewerLeaf): Span { val host = this return when (leaf.type) { - JSONType.STRING -> { + JSONType.STRING -> { span("\"${leaf.stringRes}\"") { textColor = host.styleProvider.stringColor } } - JSONType.NUMBER -> { + JSONType.NUMBER -> { span(leaf.stringRes) { textColor = host.styleProvider.numberColor } @@ -187,7 +187,7 @@ internal class JSonViewerEpoxyController(private val context: Context) : textColor = host.styleProvider.booleanColor } } - JSONType.NULL -> { + JSONType.NULL -> { span("null") { textColor = host.styleProvider.booleanColor } 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 6940e79e3f..2492b5454c 100644 --- a/library/jsonviewer/src/main/java/org/billcarsonfr/jsonviewer/JSonViewerModel.kt +++ b/library/jsonviewer/src/main/java/org/billcarsonfr/jsonviewer/JSonViewerModel.kt @@ -82,7 +82,7 @@ internal object ModelParser { } parent.addChild(objectComposed) } - is JSONArray -> { + is JSONArray -> { val objectComposed = JSonViewerArray(key, index, obj) .apply { isExpanded = initialOpenDepth == -1 || depth <= initialOpenDepth } objectComposed.depth = depth @@ -91,25 +91,25 @@ internal object ModelParser { } parent.addChild(objectComposed) } - is String -> { + 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/multipicker/build.gradle b/library/multipicker/build.gradle index bb98a2f852..2de99d5c20 100644 --- a/library/multipicker/build.gradle +++ b/library/multipicker/build.gradle @@ -38,9 +38,9 @@ android { } dependencies { - implementation libs.androidx.appCompat - implementation libs.androidx.fragmentKtx + api libs.androidx.activity implementation libs.androidx.exifinterface + implementation libs.androidx.core // Log implementation libs.jakewharton.timber diff --git a/library/multipicker/src/main/java/im/vector/lib/multipicker/FilePicker.kt b/library/multipicker/src/main/java/im/vector/lib/multipicker/FilePicker.kt index 13ef5aa637..928fdf894c 100644 --- a/library/multipicker/src/main/java/im/vector/lib/multipicker/FilePicker.kt +++ b/library/multipicker/src/main/java/im/vector/lib/multipicker/FilePicker.kt @@ -48,7 +48,7 @@ class FilePicker : Picker() { type.isMimeTypeVideo() -> selectedUri.toMultiPickerVideoType(context) type.isMimeTypeImage() -> selectedUri.toMultiPickerImageType(context) type.isMimeTypeAudio() -> selectedUri.toMultiPickerAudioType(context) - else -> { + else -> { // Other files context.contentResolver.query(selectedUri, null, null, null, null) ?.use { cursor -> 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 e7883c9e53..9377345886 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 private constructor() { @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 + 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") + else -> throw IllegalArgumentException("Unsupported type $type") } } } diff --git a/library/opusencoder/.gitignore b/library/opusencoder/.gitignore new file mode 100644 index 0000000000..ff535c85f5 --- /dev/null +++ b/library/opusencoder/.gitignore @@ -0,0 +1,10 @@ +*.iml +.gradle +/local.properties +/.idea +.DS_Store +/build +/captures +*.cxx +app/.cxx/* +.externalNativeBuild \ No newline at end of file diff --git a/library/opusencoder/build.gradle b/library/opusencoder/build.gradle new file mode 100644 index 0000000000..a825bb98bc --- /dev/null +++ b/library/opusencoder/build.gradle @@ -0,0 +1,40 @@ +apply plugin: 'com.android.library' +apply plugin: 'kotlin-android' + +android { + ndkVersion "21.3.6528147" + + compileSdkVersion 31 + + buildToolsVersion "31.0.0" + + defaultConfig { + minSdkVersion 18 + targetSdkVersion 31 + versionCode 1 + versionName "1.0" + + externalNativeBuild { + ndk { + abiFilters 'armeabi-v7a', 'arm64-v8a', 'x86', 'x86_64' + } + } + } + + buildTypes { + release { + minifyEnabled false + proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' + } + } + + externalNativeBuild { + cmake { + path "src/main/cpp/CMakeLists.txt" + } + } +} + +dependencies { + implementation libs.androidx.annotation +} diff --git a/library/opusencoder/src/main/AndroidManifest.xml b/library/opusencoder/src/main/AndroidManifest.xml new file mode 100644 index 0000000000..4dd3413820 --- /dev/null +++ b/library/opusencoder/src/main/AndroidManifest.xml @@ -0,0 +1,2 @@ + + diff --git a/library/opusencoder/src/main/cpp/CMakeLists.txt b/library/opusencoder/src/main/cpp/CMakeLists.txt new file mode 100644 index 0000000000..c261af8b90 --- /dev/null +++ b/library/opusencoder/src/main/cpp/CMakeLists.txt @@ -0,0 +1,61 @@ +# For more information about using CMake with Android Studio, read the +# documentation: https://d.android.com/studio/projects/add-native-code.html + +# Sets the minimum version of CMake required to build the native library. + +cmake_minimum_required(VERSION 3.4.1) +set(CMAKE_CXX_STANDARD 14) + +# Creates and names a library, sets it as either STATIC +# or SHARED, and provides the relative paths to its source code. +# You can define multiple libraries, and CMake builds them for you. +# Gradle automatically packages shared libraries with your APK. + +set(distribution_OPUS_DIR ${CMAKE_SOURCE_DIR}/opus) + +add_library(lib_opus SHARED IMPORTED) +set_target_properties(lib_opus PROPERTIES IMPORTED_LOCATION + ${distribution_OPUS_DIR}/libs/${ANDROID_ABI}/libopus.so) + +add_library(lib_opusenc SHARED IMPORTED) +set_target_properties(lib_opusenc PROPERTIES IMPORTED_LOCATION + ${distribution_OPUS_DIR}/libs/${ANDROID_ABI}/libopusenc.so) + +add_library( # Sets the name of the library. + opuscodec + + # Sets the library as a shared library. + SHARED + + # Provides a relative path to your source file(s). + codec/CodecOggOpus.cpp + opuscodec.cpp) + +target_include_directories(opuscodec PRIVATE + ${distribution_OPUS_DIR}/include) + +# Searches for a specified prebuilt library and stores the path as a +# variable. Because CMake includes system libraries in the search path by +# default, you only need to specify the name of the public NDK library +# you want to add. CMake verifies that the library exists before +# completing its build. + +find_library( # Sets the name of the path variable. + log-lib + + # Specifies the name of the NDK library that + # you want CMake to locate. + log ) + +# Specifies libraries CMake should link to your target library. You +# can link multiple libraries, such as libraries you define in this +# build script, prebuilt third-party libraries, or system libraries. + +target_link_libraries( # Specifies the target library. + opuscodec + android + lib_opusenc + lib_opus + # Links the target library to the log library + # included in the NDK. + ${log-lib} ) diff --git a/library/opusencoder/src/main/cpp/codec/CodecOggOpus.cpp b/library/opusencoder/src/main/cpp/codec/CodecOggOpus.cpp new file mode 100644 index 0000000000..d167c69cbf --- /dev/null +++ b/library/opusencoder/src/main/cpp/codec/CodecOggOpus.cpp @@ -0,0 +1,63 @@ +/* + * 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. + */ + +#include "CodecOggOpus.h" +#include "../utils/Logger.h" + +int ret; + +int CodecOggOpus::encoderInit(char* filePath, int sampleRate) { + // Create default, empty comment header + comments = ope_comments_create(); + // Mono audio + int numChannels = 1; + // Channel Mapping Family 0, used for mono/stereo streams + int family = 0; + // Create encoder to encode PCM chunks and write the result to a file with the OggOpus framing + encoder = ope_encoder_create_file(filePath, comments, sampleRate, numChannels, family, &ret); + if (ret != OPE_OK) { + LOGE(TAG, "Creation of OggOpusEnc failed."); + return ret; + } + return OPE_OK; +} + +int CodecOggOpus::setBitrate(int bitrate) { + ret = ope_encoder_ctl(encoder, OPUS_SET_BITRATE_REQUEST, bitrate); + if (ret != OPE_OK) { + LOGE(TAG, "Could not set bitrate."); + return ret; + } + return OPE_OK; +} + +int CodecOggOpus::writeFrame(short* frame, int samplesPerChannel) { + // Encode the raw PCM-16 buffer to Opus and write it to the ogg file + return ope_encoder_write(encoder, frame, samplesPerChannel); +} + +void CodecOggOpus::encoderRelease() { + // Finish any pending encode/write operations + ope_encoder_drain(encoder); + // De-init the encoder instance + ope_encoder_destroy(encoder); + // De-init the comment header struct + ope_comments_destroy(comments); +} + +CodecOggOpus::~CodecOggOpus() { + encoderRelease(); +} diff --git a/library/opusencoder/src/main/cpp/codec/CodecOggOpus.h b/library/opusencoder/src/main/cpp/codec/CodecOggOpus.h new file mode 100644 index 0000000000..f1035434c5 --- /dev/null +++ b/library/opusencoder/src/main/cpp/codec/CodecOggOpus.h @@ -0,0 +1,52 @@ +/* + * 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. + */ + +#ifndef ELEMENT_ANDROID_CODECOGGOPUS_H +#define ELEMENT_ANDROID_CODECOGGOPUS_H + +#include +/** + * This class is a wrapper around libopusenc, used to encode and write Opus frames into an Ogg file. + * + * The usual flow would be: + * + * 1. Use encoderInit to initialize the internal encoder with the sample rate and the path to write the encoded frames to. + * 2. (Optional) set the bitrate to use. + * 3. While recording, read PCM-16 chunks from the recorder, feed them to the encoder using writeFrame. + * 4. When finished, call encoderRelease to free some resources. + */ +class CodecOggOpus { + +private: + const char *TAG = "CodecOggOpus"; + + OggOpusEnc* encoder; + OggOpusComments* comments; + +public: + int encoderInit(char* filePath, int sampleRate); + + int setBitrate(int bitrate); + + int writeFrame(short *frame, int samplesPerChannel); + + void encoderRelease(); + + ~CodecOggOpus(); +}; + + +#endif //ELEMENT_ANDROID_CODECOGGOPUS_H diff --git a/library/opusencoder/src/main/cpp/opus/include/opus.h b/library/opusencoder/src/main/cpp/opus/include/opus.h new file mode 100644 index 0000000000..d282f21d25 --- /dev/null +++ b/library/opusencoder/src/main/cpp/opus/include/opus.h @@ -0,0 +1,981 @@ +/* Copyright (c) 2010-2011 Xiph.Org Foundation, Skype Limited + Written by Jean-Marc Valin and Koen Vos */ +/* + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + - Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + - Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER + OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +/** + * @file opus.h + * @brief Opus reference implementation API + */ + +#ifndef OPUS_H +#define OPUS_H + +#include "opus_types.h" +#include "opus_defines.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @mainpage Opus + * + * The Opus codec is designed for interactive speech and audio transmission over the Internet. + * It is designed by the IETF Codec Working Group and incorporates technology from + * Skype's SILK codec and Xiph.Org's CELT codec. + * + * The Opus codec is designed to handle a wide range of interactive audio applications, + * including Voice over IP, videoconferencing, in-game chat, and even remote live music + * performances. It can scale from low bit-rate narrowband speech to very high quality + * stereo music. Its main features are: + + * @li Sampling rates from 8 to 48 kHz + * @li Bit-rates from 6 kb/s to 510 kb/s + * @li Support for both constant bit-rate (CBR) and variable bit-rate (VBR) + * @li Audio bandwidth from narrowband to full-band + * @li Support for speech and music + * @li Support for mono and stereo + * @li Support for multichannel (up to 255 channels) + * @li Frame sizes from 2.5 ms to 60 ms + * @li Good loss robustness and packet loss concealment (PLC) + * @li Floating point and fixed-point implementation + * + * Documentation sections: + * @li @ref opus_encoder + * @li @ref opus_decoder + * @li @ref opus_repacketizer + * @li @ref opus_multistream + * @li @ref opus_libinfo + * @li @ref opus_custom + */ + +/** @defgroup opus_encoder Opus Encoder + * @{ + * + * @brief This page describes the process and functions used to encode Opus. + * + * Since Opus is a stateful codec, the encoding process starts with creating an encoder + * state. This can be done with: + * + * @code + * int error; + * OpusEncoder *enc; + * enc = opus_encoder_create(Fs, channels, application, &error); + * @endcode + * + * From this point, @c enc can be used for encoding an audio stream. An encoder state + * @b must @b not be used for more than one stream at the same time. Similarly, the encoder + * state @b must @b not be re-initialized for each frame. + * + * While opus_encoder_create() allocates memory for the state, it's also possible + * to initialize pre-allocated memory: + * + * @code + * int size; + * int error; + * OpusEncoder *enc; + * size = opus_encoder_get_size(channels); + * enc = malloc(size); + * error = opus_encoder_init(enc, Fs, channels, application); + * @endcode + * + * where opus_encoder_get_size() returns the required size for the encoder state. Note that + * future versions of this code may change the size, so no assuptions should be made about it. + * + * The encoder state is always continuous in memory and only a shallow copy is sufficient + * to copy it (e.g. memcpy()) + * + * It is possible to change some of the encoder's settings using the opus_encoder_ctl() + * interface. All these settings already default to the recommended value, so they should + * only be changed when necessary. The most common settings one may want to change are: + * + * @code + * opus_encoder_ctl(enc, OPUS_SET_BITRATE(bitrate)); + * opus_encoder_ctl(enc, OPUS_SET_COMPLEXITY(complexity)); + * opus_encoder_ctl(enc, OPUS_SET_SIGNAL(signal_type)); + * @endcode + * + * where + * + * @arg bitrate is in bits per second (b/s) + * @arg complexity is a value from 1 to 10, where 1 is the lowest complexity and 10 is the highest + * @arg signal_type is either OPUS_AUTO (default), OPUS_SIGNAL_VOICE, or OPUS_SIGNAL_MUSIC + * + * See @ref opus_encoderctls and @ref opus_genericctls for a complete list of parameters that can be set or queried. Most parameters can be set or changed at any time during a stream. + * + * To encode a frame, opus_encode() or opus_encode_float() must be called with exactly one frame (2.5, 5, 10, 20, 40 or 60 ms) of audio data: + * @code + * len = opus_encode(enc, audio_frame, frame_size, packet, max_packet); + * @endcode + * + * where + *
    + *
  • audio_frame is the audio data in opus_int16 (or float for opus_encode_float())
  • + *
  • frame_size is the duration of the frame in samples (per channel)
  • + *
  • packet is the byte array to which the compressed data is written
  • + *
  • max_packet is the maximum number of bytes that can be written in the packet (4000 bytes is recommended). + * Do not use max_packet to control VBR target bitrate, instead use the #OPUS_SET_BITRATE CTL.
  • + *
+ * + * opus_encode() and opus_encode_float() return the number of bytes actually written to the packet. + * The return value can be negative, which indicates that an error has occurred. If the return value + * is 2 bytes or less, then the packet does not need to be transmitted (DTX). + * + * Once the encoder state if no longer needed, it can be destroyed with + * + * @code + * opus_encoder_destroy(enc); + * @endcode + * + * If the encoder was created with opus_encoder_init() rather than opus_encoder_create(), + * then no action is required aside from potentially freeing the memory that was manually + * allocated for it (calling free(enc) for the example above) + * + */ + +/** Opus encoder state. + * This contains the complete state of an Opus encoder. + * It is position independent and can be freely copied. + * @see opus_encoder_create,opus_encoder_init + */ +typedef struct OpusEncoder OpusEncoder; + +/** Gets the size of an OpusEncoder structure. + * @param[in] channels int: Number of channels. + * This must be 1 or 2. + * @returns The size in bytes. + */ +OPUS_EXPORT OPUS_WARN_UNUSED_RESULT int opus_encoder_get_size(int channels); + +/** + */ + +/** Allocates and initializes an encoder state. + * There are three coding modes: + * + * @ref OPUS_APPLICATION_VOIP gives best quality at a given bitrate for voice + * signals. It enhances the input signal by high-pass filtering and + * emphasizing formants and harmonics. Optionally it includes in-band + * forward error correction to protect against packet loss. Use this + * mode for typical VoIP applications. Because of the enhancement, + * even at high bitrates the output may sound different from the input. + * + * @ref OPUS_APPLICATION_AUDIO gives best quality at a given bitrate for most + * non-voice signals like music. Use this mode for music and mixed + * (music/voice) content, broadcast, and applications requiring less + * than 15 ms of coding delay. + * + * @ref OPUS_APPLICATION_RESTRICTED_LOWDELAY configures low-delay mode that + * disables the speech-optimized mode in exchange for slightly reduced delay. + * This mode can only be set on an newly initialized or freshly reset encoder + * because it changes the codec delay. + * + * This is useful when the caller knows that the speech-optimized modes will not be needed (use with caution). + * @param [in] Fs opus_int32: Sampling rate of input signal (Hz) + * This must be one of 8000, 12000, 16000, + * 24000, or 48000. + * @param [in] channels int: Number of channels (1 or 2) in input signal + * @param [in] application int: Coding mode (@ref OPUS_APPLICATION_VOIP/@ref OPUS_APPLICATION_AUDIO/@ref OPUS_APPLICATION_RESTRICTED_LOWDELAY) + * @param [out] error int*: @ref opus_errorcodes + * @note Regardless of the sampling rate and number channels selected, the Opus encoder + * can switch to a lower audio bandwidth or number of channels if the bitrate + * selected is too low. This also means that it is safe to always use 48 kHz stereo input + * and let the encoder optimize the encoding. + */ +OPUS_EXPORT OPUS_WARN_UNUSED_RESULT OpusEncoder *opus_encoder_create( + opus_int32 Fs, + int channels, + int application, + int *error +); + +/** Initializes a previously allocated encoder state + * The memory pointed to by st must be at least the size returned by opus_encoder_get_size(). + * This is intended for applications which use their own allocator instead of malloc. + * @see opus_encoder_create(),opus_encoder_get_size() + * To reset a previously initialized state, use the #OPUS_RESET_STATE CTL. + * @param [in] st OpusEncoder*: Encoder state + * @param [in] Fs opus_int32: Sampling rate of input signal (Hz) + * This must be one of 8000, 12000, 16000, + * 24000, or 48000. + * @param [in] channels int: Number of channels (1 or 2) in input signal + * @param [in] application int: Coding mode (OPUS_APPLICATION_VOIP/OPUS_APPLICATION_AUDIO/OPUS_APPLICATION_RESTRICTED_LOWDELAY) + * @retval #OPUS_OK Success or @ref opus_errorcodes + */ +OPUS_EXPORT int opus_encoder_init( + OpusEncoder *st, + opus_int32 Fs, + int channels, + int application +) OPUS_ARG_NONNULL(1); + +/** Encodes an Opus frame. + * @param [in] st OpusEncoder*: Encoder state + * @param [in] pcm opus_int16*: Input signal (interleaved if 2 channels). length is frame_size*channels*sizeof(opus_int16) + * @param [in] frame_size int: Number of samples per channel in the + * input signal. + * This must be an Opus frame size for + * the encoder's sampling rate. + * For example, at 48 kHz the permitted + * values are 120, 240, 480, 960, 1920, + * and 2880. + * Passing in a duration of less than + * 10 ms (480 samples at 48 kHz) will + * prevent the encoder from using the LPC + * or hybrid modes. + * @param [out] data unsigned char*: Output payload. + * This must contain storage for at + * least \a max_data_bytes. + * @param [in] max_data_bytes opus_int32: Size of the allocated + * memory for the output + * payload. This may be + * used to impose an upper limit on + * the instant bitrate, but should + * not be used as the only bitrate + * control. Use #OPUS_SET_BITRATE to + * control the bitrate. + * @returns The length of the encoded packet (in bytes) on success or a + * negative error code (see @ref opus_errorcodes) on failure. + */ +OPUS_EXPORT OPUS_WARN_UNUSED_RESULT opus_int32 opus_encode( + OpusEncoder *st, + const opus_int16 *pcm, + int frame_size, + unsigned char *data, + opus_int32 max_data_bytes +) OPUS_ARG_NONNULL(1) OPUS_ARG_NONNULL(2) OPUS_ARG_NONNULL(4); + +/** Encodes an Opus frame from floating point input. + * @param [in] st OpusEncoder*: Encoder state + * @param [in] pcm float*: Input in float format (interleaved if 2 channels), with a normal range of +/-1.0. + * Samples with a range beyond +/-1.0 are supported but will + * be clipped by decoders using the integer API and should + * only be used if it is known that the far end supports + * extended dynamic range. + * length is frame_size*channels*sizeof(float) + * @param [in] frame_size int: Number of samples per channel in the + * input signal. + * This must be an Opus frame size for + * the encoder's sampling rate. + * For example, at 48 kHz the permitted + * values are 120, 240, 480, 960, 1920, + * and 2880. + * Passing in a duration of less than + * 10 ms (480 samples at 48 kHz) will + * prevent the encoder from using the LPC + * or hybrid modes. + * @param [out] data unsigned char*: Output payload. + * This must contain storage for at + * least \a max_data_bytes. + * @param [in] max_data_bytes opus_int32: Size of the allocated + * memory for the output + * payload. This may be + * used to impose an upper limit on + * the instant bitrate, but should + * not be used as the only bitrate + * control. Use #OPUS_SET_BITRATE to + * control the bitrate. + * @returns The length of the encoded packet (in bytes) on success or a + * negative error code (see @ref opus_errorcodes) on failure. + */ +OPUS_EXPORT OPUS_WARN_UNUSED_RESULT opus_int32 opus_encode_float( + OpusEncoder *st, + const float *pcm, + int frame_size, + unsigned char *data, + opus_int32 max_data_bytes +) OPUS_ARG_NONNULL(1) OPUS_ARG_NONNULL(2) OPUS_ARG_NONNULL(4); + +/** Frees an OpusEncoder allocated by opus_encoder_create(). + * @param[in] st OpusEncoder*: State to be freed. + */ +OPUS_EXPORT void opus_encoder_destroy(OpusEncoder *st); + +/** Perform a CTL function on an Opus encoder. + * + * Generally the request and subsequent arguments are generated + * by a convenience macro. + * @param st OpusEncoder*: Encoder state. + * @param request This and all remaining parameters should be replaced by one + * of the convenience macros in @ref opus_genericctls or + * @ref opus_encoderctls. + * @see opus_genericctls + * @see opus_encoderctls + */ +OPUS_EXPORT int opus_encoder_ctl(OpusEncoder *st, int request, ...) OPUS_ARG_NONNULL(1); +/**@}*/ + +/** @defgroup opus_decoder Opus Decoder + * @{ + * + * @brief This page describes the process and functions used to decode Opus. + * + * The decoding process also starts with creating a decoder + * state. This can be done with: + * @code + * int error; + * OpusDecoder *dec; + * dec = opus_decoder_create(Fs, channels, &error); + * @endcode + * where + * @li Fs is the sampling rate and must be 8000, 12000, 16000, 24000, or 48000 + * @li channels is the number of channels (1 or 2) + * @li error will hold the error code in case of failure (or #OPUS_OK on success) + * @li the return value is a newly created decoder state to be used for decoding + * + * While opus_decoder_create() allocates memory for the state, it's also possible + * to initialize pre-allocated memory: + * @code + * int size; + * int error; + * OpusDecoder *dec; + * size = opus_decoder_get_size(channels); + * dec = malloc(size); + * error = opus_decoder_init(dec, Fs, channels); + * @endcode + * where opus_decoder_get_size() returns the required size for the decoder state. Note that + * future versions of this code may change the size, so no assuptions should be made about it. + * + * The decoder state is always continuous in memory and only a shallow copy is sufficient + * to copy it (e.g. memcpy()) + * + * To decode a frame, opus_decode() or opus_decode_float() must be called with a packet of compressed audio data: + * @code + * frame_size = opus_decode(dec, packet, len, decoded, max_size, 0); + * @endcode + * where + * + * @li packet is the byte array containing the compressed data + * @li len is the exact number of bytes contained in the packet + * @li decoded is the decoded audio data in opus_int16 (or float for opus_decode_float()) + * @li max_size is the max duration of the frame in samples (per channel) that can fit into the decoded_frame array + * + * opus_decode() and opus_decode_float() return the number of samples (per channel) decoded from the packet. + * If that value is negative, then an error has occurred. This can occur if the packet is corrupted or if the audio + * buffer is too small to hold the decoded audio. + * + * Opus is a stateful codec with overlapping blocks and as a result Opus + * packets are not coded independently of each other. Packets must be + * passed into the decoder serially and in the correct order for a correct + * decode. Lost packets can be replaced with loss concealment by calling + * the decoder with a null pointer and zero length for the missing packet. + * + * A single codec state may only be accessed from a single thread at + * a time and any required locking must be performed by the caller. Separate + * streams must be decoded with separate decoder states and can be decoded + * in parallel unless the library was compiled with NONTHREADSAFE_PSEUDOSTACK + * defined. + * + */ + +/** Opus decoder state. + * This contains the complete state of an Opus decoder. + * It is position independent and can be freely copied. + * @see opus_decoder_create,opus_decoder_init + */ +typedef struct OpusDecoder OpusDecoder; + +/** Gets the size of an OpusDecoder structure. + * @param [in] channels int: Number of channels. + * This must be 1 or 2. + * @returns The size in bytes. + */ +OPUS_EXPORT OPUS_WARN_UNUSED_RESULT int opus_decoder_get_size(int channels); + +/** Allocates and initializes a decoder state. + * @param [in] Fs opus_int32: Sample rate to decode at (Hz). + * This must be one of 8000, 12000, 16000, + * 24000, or 48000. + * @param [in] channels int: Number of channels (1 or 2) to decode + * @param [out] error int*: #OPUS_OK Success or @ref opus_errorcodes + * + * Internally Opus stores data at 48000 Hz, so that should be the default + * value for Fs. However, the decoder can efficiently decode to buffers + * at 8, 12, 16, and 24 kHz so if for some reason the caller cannot use + * data at the full sample rate, or knows the compressed data doesn't + * use the full frequency range, it can request decoding at a reduced + * rate. Likewise, the decoder is capable of filling in either mono or + * interleaved stereo pcm buffers, at the caller's request. + */ +OPUS_EXPORT OPUS_WARN_UNUSED_RESULT OpusDecoder *opus_decoder_create( + opus_int32 Fs, + int channels, + int *error +); + +/** Initializes a previously allocated decoder state. + * The state must be at least the size returned by opus_decoder_get_size(). + * This is intended for applications which use their own allocator instead of malloc. @see opus_decoder_create,opus_decoder_get_size + * To reset a previously initialized state, use the #OPUS_RESET_STATE CTL. + * @param [in] st OpusDecoder*: Decoder state. + * @param [in] Fs opus_int32: Sampling rate to decode to (Hz). + * This must be one of 8000, 12000, 16000, + * 24000, or 48000. + * @param [in] channels int: Number of channels (1 or 2) to decode + * @retval #OPUS_OK Success or @ref opus_errorcodes + */ +OPUS_EXPORT int opus_decoder_init( + OpusDecoder *st, + opus_int32 Fs, + int channels +) OPUS_ARG_NONNULL(1); + +/** Decode an Opus packet. + * @param [in] st OpusDecoder*: Decoder state + * @param [in] data char*: Input payload. Use a NULL pointer to indicate packet loss + * @param [in] len opus_int32: Number of bytes in payload* + * @param [out] pcm opus_int16*: Output signal (interleaved if 2 channels). length + * is frame_size*channels*sizeof(opus_int16) + * @param [in] frame_size Number of samples per channel of available space in \a pcm. + * If this is less than the maximum packet duration (120ms; 5760 for 48kHz), this function will + * not be capable of decoding some packets. In the case of PLC (data==NULL) or FEC (decode_fec=1), + * then frame_size needs to be exactly the duration of audio that is missing, otherwise the + * decoder will not be in the optimal state to decode the next incoming packet. For the PLC and + * FEC cases, frame_size must be a multiple of 2.5 ms. + * @param [in] decode_fec int: Flag (0 or 1) to request that any in-band forward error correction data be + * decoded. If no such data is available, the frame is decoded as if it were lost. + * @returns Number of decoded samples or @ref opus_errorcodes + */ +OPUS_EXPORT OPUS_WARN_UNUSED_RESULT int opus_decode( + OpusDecoder *st, + const unsigned char *data, + opus_int32 len, + opus_int16 *pcm, + int frame_size, + int decode_fec +) OPUS_ARG_NONNULL(1) OPUS_ARG_NONNULL(4); + +/** Decode an Opus packet with floating point output. + * @param [in] st OpusDecoder*: Decoder state + * @param [in] data char*: Input payload. Use a NULL pointer to indicate packet loss + * @param [in] len opus_int32: Number of bytes in payload + * @param [out] pcm float*: Output signal (interleaved if 2 channels). length + * is frame_size*channels*sizeof(float) + * @param [in] frame_size Number of samples per channel of available space in \a pcm. + * If this is less than the maximum packet duration (120ms; 5760 for 48kHz), this function will + * not be capable of decoding some packets. In the case of PLC (data==NULL) or FEC (decode_fec=1), + * then frame_size needs to be exactly the duration of audio that is missing, otherwise the + * decoder will not be in the optimal state to decode the next incoming packet. For the PLC and + * FEC cases, frame_size must be a multiple of 2.5 ms. + * @param [in] decode_fec int: Flag (0 or 1) to request that any in-band forward error correction data be + * decoded. If no such data is available the frame is decoded as if it were lost. + * @returns Number of decoded samples or @ref opus_errorcodes + */ +OPUS_EXPORT OPUS_WARN_UNUSED_RESULT int opus_decode_float( + OpusDecoder *st, + const unsigned char *data, + opus_int32 len, + float *pcm, + int frame_size, + int decode_fec +) OPUS_ARG_NONNULL(1) OPUS_ARG_NONNULL(4); + +/** Perform a CTL function on an Opus decoder. + * + * Generally the request and subsequent arguments are generated + * by a convenience macro. + * @param st OpusDecoder*: Decoder state. + * @param request This and all remaining parameters should be replaced by one + * of the convenience macros in @ref opus_genericctls or + * @ref opus_decoderctls. + * @see opus_genericctls + * @see opus_decoderctls + */ +OPUS_EXPORT int opus_decoder_ctl(OpusDecoder *st, int request, ...) OPUS_ARG_NONNULL(1); + +/** Frees an OpusDecoder allocated by opus_decoder_create(). + * @param[in] st OpusDecoder*: State to be freed. + */ +OPUS_EXPORT void opus_decoder_destroy(OpusDecoder *st); + +/** Parse an opus packet into one or more frames. + * Opus_decode will perform this operation internally so most applications do + * not need to use this function. + * This function does not copy the frames, the returned pointers are pointers into + * the input packet. + * @param [in] data char*: Opus packet to be parsed + * @param [in] len opus_int32: size of data + * @param [out] out_toc char*: TOC pointer + * @param [out] frames char*[48] encapsulated frames + * @param [out] size opus_int16[48] sizes of the encapsulated frames + * @param [out] payload_offset int*: returns the position of the payload within the packet (in bytes) + * @returns number of frames + */ +OPUS_EXPORT int opus_packet_parse( + const unsigned char *data, + opus_int32 len, + unsigned char *out_toc, + const unsigned char *frames[48], + opus_int16 size[48], + int *payload_offset +) OPUS_ARG_NONNULL(1) OPUS_ARG_NONNULL(5); + +/** Gets the bandwidth of an Opus packet. + * @param [in] data char*: Opus packet + * @retval OPUS_BANDWIDTH_NARROWBAND Narrowband (4kHz bandpass) + * @retval OPUS_BANDWIDTH_MEDIUMBAND Mediumband (6kHz bandpass) + * @retval OPUS_BANDWIDTH_WIDEBAND Wideband (8kHz bandpass) + * @retval OPUS_BANDWIDTH_SUPERWIDEBAND Superwideband (12kHz bandpass) + * @retval OPUS_BANDWIDTH_FULLBAND Fullband (20kHz bandpass) + * @retval OPUS_INVALID_PACKET The compressed data passed is corrupted or of an unsupported type + */ +OPUS_EXPORT OPUS_WARN_UNUSED_RESULT int opus_packet_get_bandwidth(const unsigned char *data) OPUS_ARG_NONNULL(1); + +/** Gets the number of samples per frame from an Opus packet. + * @param [in] data char*: Opus packet. + * This must contain at least one byte of + * data. + * @param [in] Fs opus_int32: Sampling rate in Hz. + * This must be a multiple of 400, or + * inaccurate results will be returned. + * @returns Number of samples per frame. + */ +OPUS_EXPORT OPUS_WARN_UNUSED_RESULT int opus_packet_get_samples_per_frame(const unsigned char *data, opus_int32 Fs) OPUS_ARG_NONNULL(1); + +/** Gets the number of channels from an Opus packet. + * @param [in] data char*: Opus packet + * @returns Number of channels + * @retval OPUS_INVALID_PACKET The compressed data passed is corrupted or of an unsupported type + */ +OPUS_EXPORT OPUS_WARN_UNUSED_RESULT int opus_packet_get_nb_channels(const unsigned char *data) OPUS_ARG_NONNULL(1); + +/** Gets the number of frames in an Opus packet. + * @param [in] packet char*: Opus packet + * @param [in] len opus_int32: Length of packet + * @returns Number of frames + * @retval OPUS_BAD_ARG Insufficient data was passed to the function + * @retval OPUS_INVALID_PACKET The compressed data passed is corrupted or of an unsupported type + */ +OPUS_EXPORT OPUS_WARN_UNUSED_RESULT int opus_packet_get_nb_frames(const unsigned char packet[], opus_int32 len) OPUS_ARG_NONNULL(1); + +/** Gets the number of samples of an Opus packet. + * @param [in] packet char*: Opus packet + * @param [in] len opus_int32: Length of packet + * @param [in] Fs opus_int32: Sampling rate in Hz. + * This must be a multiple of 400, or + * inaccurate results will be returned. + * @returns Number of samples + * @retval OPUS_BAD_ARG Insufficient data was passed to the function + * @retval OPUS_INVALID_PACKET The compressed data passed is corrupted or of an unsupported type + */ +OPUS_EXPORT OPUS_WARN_UNUSED_RESULT int opus_packet_get_nb_samples(const unsigned char packet[], opus_int32 len, opus_int32 Fs) OPUS_ARG_NONNULL(1); + +/** Gets the number of samples of an Opus packet. + * @param [in] dec OpusDecoder*: Decoder state + * @param [in] packet char*: Opus packet + * @param [in] len opus_int32: Length of packet + * @returns Number of samples + * @retval OPUS_BAD_ARG Insufficient data was passed to the function + * @retval OPUS_INVALID_PACKET The compressed data passed is corrupted or of an unsupported type + */ +OPUS_EXPORT OPUS_WARN_UNUSED_RESULT int opus_decoder_get_nb_samples(const OpusDecoder *dec, const unsigned char packet[], opus_int32 len) OPUS_ARG_NONNULL(1) OPUS_ARG_NONNULL(2); + +/** Applies soft-clipping to bring a float signal within the [-1,1] range. If + * the signal is already in that range, nothing is done. If there are values + * outside of [-1,1], then the signal is clipped as smoothly as possible to + * both fit in the range and avoid creating excessive distortion in the + * process. + * @param [in,out] pcm float*: Input PCM and modified PCM + * @param [in] frame_size int Number of samples per channel to process + * @param [in] channels int: Number of channels + * @param [in,out] softclip_mem float*: State memory for the soft clipping process (one float per channel, initialized to zero) + */ +OPUS_EXPORT void opus_pcm_soft_clip(float *pcm, int frame_size, int channels, float *softclip_mem); + + +/**@}*/ + +/** @defgroup opus_repacketizer Repacketizer + * @{ + * + * The repacketizer can be used to merge multiple Opus packets into a single + * packet or alternatively to split Opus packets that have previously been + * merged. Splitting valid Opus packets is always guaranteed to succeed, + * whereas merging valid packets only succeeds if all frames have the same + * mode, bandwidth, and frame size, and when the total duration of the merged + * packet is no more than 120 ms. The 120 ms limit comes from the + * specification and limits decoder memory requirements at a point where + * framing overhead becomes negligible. + * + * The repacketizer currently only operates on elementary Opus + * streams. It will not manipualte multistream packets successfully, except in + * the degenerate case where they consist of data from a single stream. + * + * The repacketizing process starts with creating a repacketizer state, either + * by calling opus_repacketizer_create() or by allocating the memory yourself, + * e.g., + * @code + * OpusRepacketizer *rp; + * rp = (OpusRepacketizer*)malloc(opus_repacketizer_get_size()); + * if (rp != NULL) + * opus_repacketizer_init(rp); + * @endcode + * + * Then the application should submit packets with opus_repacketizer_cat(), + * extract new packets with opus_repacketizer_out() or + * opus_repacketizer_out_range(), and then reset the state for the next set of + * input packets via opus_repacketizer_init(). + * + * For example, to split a sequence of packets into individual frames: + * @code + * unsigned char *data; + * int len; + * while (get_next_packet(&data, &len)) + * { + * unsigned char out[1276]; + * opus_int32 out_len; + * int nb_frames; + * int err; + * int i; + * err = opus_repacketizer_cat(rp, data, len); + * if (err != OPUS_OK) + * { + * release_packet(data); + * return err; + * } + * nb_frames = opus_repacketizer_get_nb_frames(rp); + * for (i = 0; i < nb_frames; i++) + * { + * out_len = opus_repacketizer_out_range(rp, i, i+1, out, sizeof(out)); + * if (out_len < 0) + * { + * release_packet(data); + * return (int)out_len; + * } + * output_next_packet(out, out_len); + * } + * opus_repacketizer_init(rp); + * release_packet(data); + * } + * @endcode + * + * Alternatively, to combine a sequence of frames into packets that each + * contain up to TARGET_DURATION_MS milliseconds of data: + * @code + * // The maximum number of packets with duration TARGET_DURATION_MS occurs + * // when the frame size is 2.5 ms, for a total of (TARGET_DURATION_MS*2/5) + * // packets. + * unsigned char *data[(TARGET_DURATION_MS*2/5)+1]; + * opus_int32 len[(TARGET_DURATION_MS*2/5)+1]; + * int nb_packets; + * unsigned char out[1277*(TARGET_DURATION_MS*2/2)]; + * opus_int32 out_len; + * int prev_toc; + * nb_packets = 0; + * while (get_next_packet(data+nb_packets, len+nb_packets)) + * { + * int nb_frames; + * int err; + * nb_frames = opus_packet_get_nb_frames(data[nb_packets], len[nb_packets]); + * if (nb_frames < 1) + * { + * release_packets(data, nb_packets+1); + * return nb_frames; + * } + * nb_frames += opus_repacketizer_get_nb_frames(rp); + * // If adding the next packet would exceed our target, or it has an + * // incompatible TOC sequence, output the packets we already have before + * // submitting it. + * // N.B., The nb_packets > 0 check ensures we've submitted at least one + * // packet since the last call to opus_repacketizer_init(). Otherwise a + * // single packet longer than TARGET_DURATION_MS would cause us to try to + * // output an (invalid) empty packet. It also ensures that prev_toc has + * // been set to a valid value. Additionally, len[nb_packets] > 0 is + * // guaranteed by the call to opus_packet_get_nb_frames() above, so the + * // reference to data[nb_packets][0] should be valid. + * if (nb_packets > 0 && ( + * ((prev_toc & 0xFC) != (data[nb_packets][0] & 0xFC)) || + * opus_packet_get_samples_per_frame(data[nb_packets], 48000)*nb_frames > + * TARGET_DURATION_MS*48)) + * { + * out_len = opus_repacketizer_out(rp, out, sizeof(out)); + * if (out_len < 0) + * { + * release_packets(data, nb_packets+1); + * return (int)out_len; + * } + * output_next_packet(out, out_len); + * opus_repacketizer_init(rp); + * release_packets(data, nb_packets); + * data[0] = data[nb_packets]; + * len[0] = len[nb_packets]; + * nb_packets = 0; + * } + * err = opus_repacketizer_cat(rp, data[nb_packets], len[nb_packets]); + * if (err != OPUS_OK) + * { + * release_packets(data, nb_packets+1); + * return err; + * } + * prev_toc = data[nb_packets][0]; + * nb_packets++; + * } + * // Output the final, partial packet. + * if (nb_packets > 0) + * { + * out_len = opus_repacketizer_out(rp, out, sizeof(out)); + * release_packets(data, nb_packets); + * if (out_len < 0) + * return (int)out_len; + * output_next_packet(out, out_len); + * } + * @endcode + * + * An alternate way of merging packets is to simply call opus_repacketizer_cat() + * unconditionally until it fails. At that point, the merged packet can be + * obtained with opus_repacketizer_out() and the input packet for which + * opus_repacketizer_cat() needs to be re-added to a newly reinitialized + * repacketizer state. + */ + +typedef struct OpusRepacketizer OpusRepacketizer; + +/** Gets the size of an OpusRepacketizer structure. + * @returns The size in bytes. + */ +OPUS_EXPORT OPUS_WARN_UNUSED_RESULT int opus_repacketizer_get_size(void); + +/** (Re)initializes a previously allocated repacketizer state. + * The state must be at least the size returned by opus_repacketizer_get_size(). + * This can be used for applications which use their own allocator instead of + * malloc(). + * It must also be called to reset the queue of packets waiting to be + * repacketized, which is necessary if the maximum packet duration of 120 ms + * is reached or if you wish to submit packets with a different Opus + * configuration (coding mode, audio bandwidth, frame size, or channel count). + * Failure to do so will prevent a new packet from being added with + * opus_repacketizer_cat(). + * @see opus_repacketizer_create + * @see opus_repacketizer_get_size + * @see opus_repacketizer_cat + * @param rp OpusRepacketizer*: The repacketizer state to + * (re)initialize. + * @returns A pointer to the same repacketizer state that was passed in. + */ +OPUS_EXPORT OpusRepacketizer *opus_repacketizer_init(OpusRepacketizer *rp) OPUS_ARG_NONNULL(1); + +/** Allocates memory and initializes the new repacketizer with + * opus_repacketizer_init(). + */ +OPUS_EXPORT OPUS_WARN_UNUSED_RESULT OpusRepacketizer *opus_repacketizer_create(void); + +/** Frees an OpusRepacketizer allocated by + * opus_repacketizer_create(). + * @param[in] rp OpusRepacketizer*: State to be freed. + */ +OPUS_EXPORT void opus_repacketizer_destroy(OpusRepacketizer *rp); + +/** Add a packet to the current repacketizer state. + * This packet must match the configuration of any packets already submitted + * for repacketization since the last call to opus_repacketizer_init(). + * This means that it must have the same coding mode, audio bandwidth, frame + * size, and channel count. + * This can be checked in advance by examining the top 6 bits of the first + * byte of the packet, and ensuring they match the top 6 bits of the first + * byte of any previously submitted packet. + * The total duration of audio in the repacketizer state also must not exceed + * 120 ms, the maximum duration of a single packet, after adding this packet. + * + * The contents of the current repacketizer state can be extracted into new + * packets using opus_repacketizer_out() or opus_repacketizer_out_range(). + * + * In order to add a packet with a different configuration or to add more + * audio beyond 120 ms, you must clear the repacketizer state by calling + * opus_repacketizer_init(). + * If a packet is too large to add to the current repacketizer state, no part + * of it is added, even if it contains multiple frames, some of which might + * fit. + * If you wish to be able to add parts of such packets, you should first use + * another repacketizer to split the packet into pieces and add them + * individually. + * @see opus_repacketizer_out_range + * @see opus_repacketizer_out + * @see opus_repacketizer_init + * @param rp OpusRepacketizer*: The repacketizer state to which to + * add the packet. + * @param[in] data const unsigned char*: The packet data. + * The application must ensure + * this pointer remains valid + * until the next call to + * opus_repacketizer_init() or + * opus_repacketizer_destroy(). + * @param len opus_int32: The number of bytes in the packet data. + * @returns An error code indicating whether or not the operation succeeded. + * @retval #OPUS_OK The packet's contents have been added to the repacketizer + * state. + * @retval #OPUS_INVALID_PACKET The packet did not have a valid TOC sequence, + * the packet's TOC sequence was not compatible + * with previously submitted packets (because + * the coding mode, audio bandwidth, frame size, + * or channel count did not match), or adding + * this packet would increase the total amount of + * audio stored in the repacketizer state to more + * than 120 ms. + */ +OPUS_EXPORT int opus_repacketizer_cat(OpusRepacketizer *rp, const unsigned char *data, opus_int32 len) OPUS_ARG_NONNULL(1) OPUS_ARG_NONNULL(2); + + +/** Construct a new packet from data previously submitted to the repacketizer + * state via opus_repacketizer_cat(). + * @param rp OpusRepacketizer*: The repacketizer state from which to + * construct the new packet. + * @param begin int: The index of the first frame in the current + * repacketizer state to include in the output. + * @param end int: One past the index of the last frame in the + * current repacketizer state to include in the + * output. + * @param[out] data const unsigned char*: The buffer in which to + * store the output packet. + * @param maxlen opus_int32: The maximum number of bytes to store in + * the output buffer. In order to guarantee + * success, this should be at least + * 1276 for a single frame, + * or for multiple frames, + * 1277*(end-begin). + * However, 1*(end-begin) plus + * the size of all packet data submitted to + * the repacketizer since the last call to + * opus_repacketizer_init() or + * opus_repacketizer_create() is also + * sufficient, and possibly much smaller. + * @returns The total size of the output packet on success, or an error code + * on failure. + * @retval #OPUS_BAD_ARG [begin,end) was an invalid range of + * frames (begin < 0, begin >= end, or end > + * opus_repacketizer_get_nb_frames()). + * @retval #OPUS_BUFFER_TOO_SMALL \a maxlen was insufficient to contain the + * complete output packet. + */ +OPUS_EXPORT OPUS_WARN_UNUSED_RESULT opus_int32 opus_repacketizer_out_range(OpusRepacketizer *rp, int begin, int end, unsigned char *data, opus_int32 maxlen) OPUS_ARG_NONNULL(1) OPUS_ARG_NONNULL(4); + +/** Return the total number of frames contained in packet data submitted to + * the repacketizer state so far via opus_repacketizer_cat() since the last + * call to opus_repacketizer_init() or opus_repacketizer_create(). + * This defines the valid range of packets that can be extracted with + * opus_repacketizer_out_range() or opus_repacketizer_out(). + * @param rp OpusRepacketizer*: The repacketizer state containing the + * frames. + * @returns The total number of frames contained in the packet data submitted + * to the repacketizer state. + */ +OPUS_EXPORT OPUS_WARN_UNUSED_RESULT int opus_repacketizer_get_nb_frames(OpusRepacketizer *rp) OPUS_ARG_NONNULL(1); + +/** Construct a new packet from data previously submitted to the repacketizer + * state via opus_repacketizer_cat(). + * This is a convenience routine that returns all the data submitted so far + * in a single packet. + * It is equivalent to calling + * @code + * opus_repacketizer_out_range(rp, 0, opus_repacketizer_get_nb_frames(rp), + * data, maxlen) + * @endcode + * @param rp OpusRepacketizer*: The repacketizer state from which to + * construct the new packet. + * @param[out] data const unsigned char*: The buffer in which to + * store the output packet. + * @param maxlen opus_int32: The maximum number of bytes to store in + * the output buffer. In order to guarantee + * success, this should be at least + * 1277*opus_repacketizer_get_nb_frames(rp). + * However, + * 1*opus_repacketizer_get_nb_frames(rp) + * plus the size of all packet data + * submitted to the repacketizer since the + * last call to opus_repacketizer_init() or + * opus_repacketizer_create() is also + * sufficient, and possibly much smaller. + * @returns The total size of the output packet on success, or an error code + * on failure. + * @retval #OPUS_BUFFER_TOO_SMALL \a maxlen was insufficient to contain the + * complete output packet. + */ +OPUS_EXPORT OPUS_WARN_UNUSED_RESULT opus_int32 opus_repacketizer_out(OpusRepacketizer *rp, unsigned char *data, opus_int32 maxlen) OPUS_ARG_NONNULL(1); + +/** Pads a given Opus packet to a larger size (possibly changing the TOC sequence). + * @param[in,out] data const unsigned char*: The buffer containing the + * packet to pad. + * @param len opus_int32: The size of the packet. + * This must be at least 1. + * @param new_len opus_int32: The desired size of the packet after padding. + * This must be at least as large as len. + * @returns an error code + * @retval #OPUS_OK \a on success. + * @retval #OPUS_BAD_ARG \a len was less than 1 or new_len was less than len. + * @retval #OPUS_INVALID_PACKET \a data did not contain a valid Opus packet. + */ +OPUS_EXPORT int opus_packet_pad(unsigned char *data, opus_int32 len, opus_int32 new_len); + +/** Remove all padding from a given Opus packet and rewrite the TOC sequence to + * minimize space usage. + * @param[in,out] data const unsigned char*: The buffer containing the + * packet to strip. + * @param len opus_int32: The size of the packet. + * This must be at least 1. + * @returns The new size of the output packet on success, or an error code + * on failure. + * @retval #OPUS_BAD_ARG \a len was less than 1. + * @retval #OPUS_INVALID_PACKET \a data did not contain a valid Opus packet. + */ +OPUS_EXPORT OPUS_WARN_UNUSED_RESULT opus_int32 opus_packet_unpad(unsigned char *data, opus_int32 len); + +/** Pads a given Opus multi-stream packet to a larger size (possibly changing the TOC sequence). + * @param[in,out] data const unsigned char*: The buffer containing the + * packet to pad. + * @param len opus_int32: The size of the packet. + * This must be at least 1. + * @param new_len opus_int32: The desired size of the packet after padding. + * This must be at least 1. + * @param nb_streams opus_int32: The number of streams (not channels) in the packet. + * This must be at least as large as len. + * @returns an error code + * @retval #OPUS_OK \a on success. + * @retval #OPUS_BAD_ARG \a len was less than 1. + * @retval #OPUS_INVALID_PACKET \a data did not contain a valid Opus packet. + */ +OPUS_EXPORT int opus_multistream_packet_pad(unsigned char *data, opus_int32 len, opus_int32 new_len, int nb_streams); + +/** Remove all padding from a given Opus multi-stream packet and rewrite the TOC sequence to + * minimize space usage. + * @param[in,out] data const unsigned char*: The buffer containing the + * packet to strip. + * @param len opus_int32: The size of the packet. + * This must be at least 1. + * @param nb_streams opus_int32: The number of streams (not channels) in the packet. + * This must be at least 1. + * @returns The new size of the output packet on success, or an error code + * on failure. + * @retval #OPUS_BAD_ARG \a len was less than 1 or new_len was less than len. + * @retval #OPUS_INVALID_PACKET \a data did not contain a valid Opus packet. + */ +OPUS_EXPORT OPUS_WARN_UNUSED_RESULT opus_int32 opus_multistream_packet_unpad(unsigned char *data, opus_int32 len, int nb_streams); + +/**@}*/ + +#ifdef __cplusplus +} +#endif + +#endif /* OPUS_H */ diff --git a/library/opusencoder/src/main/cpp/opus/include/opus_custom.h b/library/opusencoder/src/main/cpp/opus/include/opus_custom.h new file mode 100644 index 0000000000..41f36bf2fb --- /dev/null +++ b/library/opusencoder/src/main/cpp/opus/include/opus_custom.h @@ -0,0 +1,342 @@ +/* Copyright (c) 2007-2008 CSIRO + Copyright (c) 2007-2009 Xiph.Org Foundation + Copyright (c) 2008-2012 Gregory Maxwell + Written by Jean-Marc Valin and Gregory Maxwell */ +/* + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + - Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + - Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER + OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +/** + @file opus_custom.h + @brief Opus-Custom reference implementation API + */ + +#ifndef OPUS_CUSTOM_H +#define OPUS_CUSTOM_H + +#include "opus_defines.h" + +#ifdef __cplusplus +extern "C" { +#endif + +#ifdef CUSTOM_MODES +# define OPUS_CUSTOM_EXPORT OPUS_EXPORT +# define OPUS_CUSTOM_EXPORT_STATIC OPUS_EXPORT +#else +# define OPUS_CUSTOM_EXPORT +# ifdef OPUS_BUILD +# define OPUS_CUSTOM_EXPORT_STATIC static OPUS_INLINE +# else +# define OPUS_CUSTOM_EXPORT_STATIC +# endif +#endif + +/** @defgroup opus_custom Opus Custom + * @{ + * Opus Custom is an optional part of the Opus specification and + * reference implementation which uses a distinct API from the regular + * API and supports frame sizes that are not normally supported.\ Use + * of Opus Custom is discouraged for all but very special applications + * for which a frame size different from 2.5, 5, 10, or 20 ms is needed + * (for either complexity or latency reasons) and where interoperability + * is less important. + * + * In addition to the interoperability limitations the use of Opus custom + * disables a substantial chunk of the codec and generally lowers the + * quality available at a given bitrate. Normally when an application needs + * a different frame size from the codec it should buffer to match the + * sizes but this adds a small amount of delay which may be important + * in some very low latency applications. Some transports (especially + * constant rate RF transports) may also work best with frames of + * particular durations. + * + * Libopus only supports custom modes if they are enabled at compile time. + * + * The Opus Custom API is similar to the regular API but the + * @ref opus_encoder_create and @ref opus_decoder_create calls take + * an additional mode parameter which is a structure produced by + * a call to @ref opus_custom_mode_create. Both the encoder and decoder + * must create a mode using the same sample rate (fs) and frame size + * (frame size) so these parameters must either be signaled out of band + * or fixed in a particular implementation. + * + * Similar to regular Opus the custom modes support on the fly frame size + * switching, but the sizes available depend on the particular frame size in + * use. For some initial frame sizes on a single on the fly size is available. + */ + +/** Contains the state of an encoder. One encoder state is needed + for each stream. It is initialized once at the beginning of the + stream. Do *not* re-initialize the state for every frame. + @brief Encoder state + */ +typedef struct OpusCustomEncoder OpusCustomEncoder; + +/** State of the decoder. One decoder state is needed for each stream. + It is initialized once at the beginning of the stream. Do *not* + re-initialize the state for every frame. + @brief Decoder state + */ +typedef struct OpusCustomDecoder OpusCustomDecoder; + +/** The mode contains all the information necessary to create an + encoder. Both the encoder and decoder need to be initialized + with exactly the same mode, otherwise the output will be + corrupted. + @brief Mode configuration + */ +typedef struct OpusCustomMode OpusCustomMode; + +/** Creates a new mode struct. This will be passed to an encoder or + * decoder. The mode MUST NOT BE DESTROYED until the encoders and + * decoders that use it are destroyed as well. + * @param [in] Fs int: Sampling rate (8000 to 96000 Hz) + * @param [in] frame_size int: Number of samples (per channel) to encode in each + * packet (64 - 1024, prime factorization must contain zero or more 2s, 3s, or 5s and no other primes) + * @param [out] error int*: Returned error code (if NULL, no error will be returned) + * @return A newly created mode + */ +OPUS_CUSTOM_EXPORT OPUS_WARN_UNUSED_RESULT OpusCustomMode *opus_custom_mode_create(opus_int32 Fs, int frame_size, int *error); + +/** Destroys a mode struct. Only call this after all encoders and + * decoders using this mode are destroyed as well. + * @param [in] mode OpusCustomMode*: Mode to be freed. + */ +OPUS_CUSTOM_EXPORT void opus_custom_mode_destroy(OpusCustomMode *mode); + + +#if !defined(OPUS_BUILD) || defined(CELT_ENCODER_C) + +/* Encoder */ +/** Gets the size of an OpusCustomEncoder structure. + * @param [in] mode OpusCustomMode *: Mode configuration + * @param [in] channels int: Number of channels + * @returns size + */ +OPUS_CUSTOM_EXPORT_STATIC OPUS_WARN_UNUSED_RESULT int opus_custom_encoder_get_size( + const OpusCustomMode *mode, + int channels +) OPUS_ARG_NONNULL(1); + +# ifdef CUSTOM_MODES +/** Initializes a previously allocated encoder state + * The memory pointed to by st must be the size returned by opus_custom_encoder_get_size. + * This is intended for applications which use their own allocator instead of malloc. + * @see opus_custom_encoder_create(),opus_custom_encoder_get_size() + * To reset a previously initialized state use the OPUS_RESET_STATE CTL. + * @param [in] st OpusCustomEncoder*: Encoder state + * @param [in] mode OpusCustomMode *: Contains all the information about the characteristics of + * the stream (must be the same characteristics as used for the + * decoder) + * @param [in] channels int: Number of channels + * @return OPUS_OK Success or @ref opus_errorcodes + */ +OPUS_CUSTOM_EXPORT int opus_custom_encoder_init( + OpusCustomEncoder *st, + const OpusCustomMode *mode, + int channels +) OPUS_ARG_NONNULL(1) OPUS_ARG_NONNULL(2); +# endif +#endif + + +/** Creates a new encoder state. Each stream needs its own encoder + * state (can't be shared across simultaneous streams). + * @param [in] mode OpusCustomMode*: Contains all the information about the characteristics of + * the stream (must be the same characteristics as used for the + * decoder) + * @param [in] channels int: Number of channels + * @param [out] error int*: Returns an error code + * @return Newly created encoder state. +*/ +OPUS_CUSTOM_EXPORT OPUS_WARN_UNUSED_RESULT OpusCustomEncoder *opus_custom_encoder_create( + const OpusCustomMode *mode, + int channels, + int *error +) OPUS_ARG_NONNULL(1); + + +/** Destroys a an encoder state. + * @param[in] st OpusCustomEncoder*: State to be freed. + */ +OPUS_CUSTOM_EXPORT void opus_custom_encoder_destroy(OpusCustomEncoder *st); + +/** Encodes a frame of audio. + * @param [in] st OpusCustomEncoder*: Encoder state + * @param [in] pcm float*: PCM audio in float format, with a normal range of +/-1.0. + * Samples with a range beyond +/-1.0 are supported but will + * be clipped by decoders using the integer API and should + * only be used if it is known that the far end supports + * extended dynamic range. There must be exactly + * frame_size samples per channel. + * @param [in] frame_size int: Number of samples per frame of input signal + * @param [out] compressed char *: The compressed data is written here. This may not alias pcm and must be at least maxCompressedBytes long. + * @param [in] maxCompressedBytes int: Maximum number of bytes to use for compressing the frame + * (can change from one frame to another) + * @return Number of bytes written to "compressed". + * If negative, an error has occurred (see error codes). It is IMPORTANT that + * the length returned be somehow transmitted to the decoder. Otherwise, no + * decoding is possible. + */ +OPUS_CUSTOM_EXPORT OPUS_WARN_UNUSED_RESULT int opus_custom_encode_float( + OpusCustomEncoder *st, + const float *pcm, + int frame_size, + unsigned char *compressed, + int maxCompressedBytes +) OPUS_ARG_NONNULL(1) OPUS_ARG_NONNULL(2) OPUS_ARG_NONNULL(4); + +/** Encodes a frame of audio. + * @param [in] st OpusCustomEncoder*: Encoder state + * @param [in] pcm opus_int16*: PCM audio in signed 16-bit format (native endian). + * There must be exactly frame_size samples per channel. + * @param [in] frame_size int: Number of samples per frame of input signal + * @param [out] compressed char *: The compressed data is written here. This may not alias pcm and must be at least maxCompressedBytes long. + * @param [in] maxCompressedBytes int: Maximum number of bytes to use for compressing the frame + * (can change from one frame to another) + * @return Number of bytes written to "compressed". + * If negative, an error has occurred (see error codes). It is IMPORTANT that + * the length returned be somehow transmitted to the decoder. Otherwise, no + * decoding is possible. + */ +OPUS_CUSTOM_EXPORT OPUS_WARN_UNUSED_RESULT int opus_custom_encode( + OpusCustomEncoder *st, + const opus_int16 *pcm, + int frame_size, + unsigned char *compressed, + int maxCompressedBytes +) OPUS_ARG_NONNULL(1) OPUS_ARG_NONNULL(2) OPUS_ARG_NONNULL(4); + +/** Perform a CTL function on an Opus custom encoder. + * + * Generally the request and subsequent arguments are generated + * by a convenience macro. + * @see opus_encoderctls + */ +OPUS_CUSTOM_EXPORT int opus_custom_encoder_ctl(OpusCustomEncoder * OPUS_RESTRICT st, int request, ...) OPUS_ARG_NONNULL(1); + + +#if !defined(OPUS_BUILD) || defined(CELT_DECODER_C) +/* Decoder */ + +/** Gets the size of an OpusCustomDecoder structure. + * @param [in] mode OpusCustomMode *: Mode configuration + * @param [in] channels int: Number of channels + * @returns size + */ +OPUS_CUSTOM_EXPORT_STATIC OPUS_WARN_UNUSED_RESULT int opus_custom_decoder_get_size( + const OpusCustomMode *mode, + int channels +) OPUS_ARG_NONNULL(1); + +/** Initializes a previously allocated decoder state + * The memory pointed to by st must be the size returned by opus_custom_decoder_get_size. + * This is intended for applications which use their own allocator instead of malloc. + * @see opus_custom_decoder_create(),opus_custom_decoder_get_size() + * To reset a previously initialized state use the OPUS_RESET_STATE CTL. + * @param [in] st OpusCustomDecoder*: Decoder state + * @param [in] mode OpusCustomMode *: Contains all the information about the characteristics of + * the stream (must be the same characteristics as used for the + * encoder) + * @param [in] channels int: Number of channels + * @return OPUS_OK Success or @ref opus_errorcodes + */ +OPUS_CUSTOM_EXPORT_STATIC int opus_custom_decoder_init( + OpusCustomDecoder *st, + const OpusCustomMode *mode, + int channels +) OPUS_ARG_NONNULL(1) OPUS_ARG_NONNULL(2); + +#endif + + +/** Creates a new decoder state. Each stream needs its own decoder state (can't + * be shared across simultaneous streams). + * @param [in] mode OpusCustomMode: Contains all the information about the characteristics of the + * stream (must be the same characteristics as used for the encoder) + * @param [in] channels int: Number of channels + * @param [out] error int*: Returns an error code + * @return Newly created decoder state. + */ +OPUS_CUSTOM_EXPORT OPUS_WARN_UNUSED_RESULT OpusCustomDecoder *opus_custom_decoder_create( + const OpusCustomMode *mode, + int channels, + int *error +) OPUS_ARG_NONNULL(1); + +/** Destroys a an decoder state. + * @param[in] st OpusCustomDecoder*: State to be freed. + */ +OPUS_CUSTOM_EXPORT void opus_custom_decoder_destroy(OpusCustomDecoder *st); + +/** Decode an opus custom frame with floating point output + * @param [in] st OpusCustomDecoder*: Decoder state + * @param [in] data char*: Input payload. Use a NULL pointer to indicate packet loss + * @param [in] len int: Number of bytes in payload + * @param [out] pcm float*: Output signal (interleaved if 2 channels). length + * is frame_size*channels*sizeof(float) + * @param [in] frame_size Number of samples per channel of available space in *pcm. + * @returns Number of decoded samples or @ref opus_errorcodes + */ +OPUS_CUSTOM_EXPORT OPUS_WARN_UNUSED_RESULT int opus_custom_decode_float( + OpusCustomDecoder *st, + const unsigned char *data, + int len, + float *pcm, + int frame_size +) OPUS_ARG_NONNULL(1) OPUS_ARG_NONNULL(4); + +/** Decode an opus custom frame + * @param [in] st OpusCustomDecoder*: Decoder state + * @param [in] data char*: Input payload. Use a NULL pointer to indicate packet loss + * @param [in] len int: Number of bytes in payload + * @param [out] pcm opus_int16*: Output signal (interleaved if 2 channels). length + * is frame_size*channels*sizeof(opus_int16) + * @param [in] frame_size Number of samples per channel of available space in *pcm. + * @returns Number of decoded samples or @ref opus_errorcodes + */ +OPUS_CUSTOM_EXPORT OPUS_WARN_UNUSED_RESULT int opus_custom_decode( + OpusCustomDecoder *st, + const unsigned char *data, + int len, + opus_int16 *pcm, + int frame_size +) OPUS_ARG_NONNULL(1) OPUS_ARG_NONNULL(4); + +/** Perform a CTL function on an Opus custom decoder. + * + * Generally the request and subsequent arguments are generated + * by a convenience macro. + * @see opus_genericctls + */ +OPUS_CUSTOM_EXPORT int opus_custom_decoder_ctl(OpusCustomDecoder * OPUS_RESTRICT st, int request, ...) OPUS_ARG_NONNULL(1); + +/**@}*/ + +#ifdef __cplusplus +} +#endif + +#endif /* OPUS_CUSTOM_H */ diff --git a/library/opusencoder/src/main/cpp/opus/include/opus_defines.h b/library/opusencoder/src/main/cpp/opus/include/opus_defines.h new file mode 100644 index 0000000000..fbf5d0eb74 --- /dev/null +++ b/library/opusencoder/src/main/cpp/opus/include/opus_defines.h @@ -0,0 +1,788 @@ +/* Copyright (c) 2010-2011 Xiph.Org Foundation, Skype Limited + Written by Jean-Marc Valin and Koen Vos */ +/* + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + - Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + - Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER + OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +/** + * @file opus_defines.h + * @brief Opus reference implementation constants + */ + +#ifndef OPUS_DEFINES_H +#define OPUS_DEFINES_H + +#include "opus_types.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/** @defgroup opus_errorcodes Error codes + * @{ + */ +/** No error @hideinitializer*/ +#define OPUS_OK 0 +/** One or more invalid/out of range arguments @hideinitializer*/ +#define OPUS_BAD_ARG -1 +/** Not enough bytes allocated in the buffer @hideinitializer*/ +#define OPUS_BUFFER_TOO_SMALL -2 +/** An internal error was detected @hideinitializer*/ +#define OPUS_INTERNAL_ERROR -3 +/** The compressed data passed is corrupted @hideinitializer*/ +#define OPUS_INVALID_PACKET -4 +/** Invalid/unsupported request number @hideinitializer*/ +#define OPUS_UNIMPLEMENTED -5 +/** An encoder or decoder structure is invalid or already freed @hideinitializer*/ +#define OPUS_INVALID_STATE -6 +/** Memory allocation has failed @hideinitializer*/ +#define OPUS_ALLOC_FAIL -7 +/**@}*/ + +/** @cond OPUS_INTERNAL_DOC */ +/**Export control for opus functions */ + +#ifndef OPUS_EXPORT +# if defined(WIN32) +# if defined(OPUS_BUILD) && defined(DLL_EXPORT) +# define OPUS_EXPORT __declspec(dllexport) +# else +# define OPUS_EXPORT +# endif +# elif defined(__GNUC__) && defined(OPUS_BUILD) +# define OPUS_EXPORT __attribute__ ((visibility ("default"))) +# else +# define OPUS_EXPORT +# endif +#endif + +# if !defined(OPUS_GNUC_PREREQ) +# if defined(__GNUC__)&&defined(__GNUC_MINOR__) +# define OPUS_GNUC_PREREQ(_maj,_min) \ + ((__GNUC__<<16)+__GNUC_MINOR__>=((_maj)<<16)+(_min)) +# else +# define OPUS_GNUC_PREREQ(_maj,_min) 0 +# endif +# endif + +#if (!defined(__STDC_VERSION__) || (__STDC_VERSION__ < 199901L) ) +# if OPUS_GNUC_PREREQ(3,0) +# define OPUS_RESTRICT __restrict__ +# elif (defined(_MSC_VER) && _MSC_VER >= 1400) +# define OPUS_RESTRICT __restrict +# else +# define OPUS_RESTRICT +# endif +#else +# define OPUS_RESTRICT restrict +#endif + +#if (!defined(__STDC_VERSION__) || (__STDC_VERSION__ < 199901L) ) +# if OPUS_GNUC_PREREQ(2,7) +# define OPUS_INLINE __inline__ +# elif (defined(_MSC_VER)) +# define OPUS_INLINE __inline +# else +# define OPUS_INLINE +# endif +#else +# define OPUS_INLINE inline +#endif + +/**Warning attributes for opus functions + * NONNULL is not used in OPUS_BUILD to avoid the compiler optimizing out + * some paranoid null checks. */ +#if defined(__GNUC__) && OPUS_GNUC_PREREQ(3, 4) +# define OPUS_WARN_UNUSED_RESULT __attribute__ ((__warn_unused_result__)) +#else +# define OPUS_WARN_UNUSED_RESULT +#endif +#if !defined(OPUS_BUILD) && defined(__GNUC__) && OPUS_GNUC_PREREQ(3, 4) +# define OPUS_ARG_NONNULL(_x) __attribute__ ((__nonnull__(_x))) +#else +# define OPUS_ARG_NONNULL(_x) +#endif + +/** These are the actual Encoder CTL ID numbers. + * They should not be used directly by applications. + * In general, SETs should be even and GETs should be odd.*/ +#define OPUS_SET_APPLICATION_REQUEST 4000 +#define OPUS_GET_APPLICATION_REQUEST 4001 +#define OPUS_SET_BITRATE_REQUEST 4002 +#define OPUS_GET_BITRATE_REQUEST 4003 +#define OPUS_SET_MAX_BANDWIDTH_REQUEST 4004 +#define OPUS_GET_MAX_BANDWIDTH_REQUEST 4005 +#define OPUS_SET_VBR_REQUEST 4006 +#define OPUS_GET_VBR_REQUEST 4007 +#define OPUS_SET_BANDWIDTH_REQUEST 4008 +#define OPUS_GET_BANDWIDTH_REQUEST 4009 +#define OPUS_SET_COMPLEXITY_REQUEST 4010 +#define OPUS_GET_COMPLEXITY_REQUEST 4011 +#define OPUS_SET_INBAND_FEC_REQUEST 4012 +#define OPUS_GET_INBAND_FEC_REQUEST 4013 +#define OPUS_SET_PACKET_LOSS_PERC_REQUEST 4014 +#define OPUS_GET_PACKET_LOSS_PERC_REQUEST 4015 +#define OPUS_SET_DTX_REQUEST 4016 +#define OPUS_GET_DTX_REQUEST 4017 +#define OPUS_SET_VBR_CONSTRAINT_REQUEST 4020 +#define OPUS_GET_VBR_CONSTRAINT_REQUEST 4021 +#define OPUS_SET_FORCE_CHANNELS_REQUEST 4022 +#define OPUS_GET_FORCE_CHANNELS_REQUEST 4023 +#define OPUS_SET_SIGNAL_REQUEST 4024 +#define OPUS_GET_SIGNAL_REQUEST 4025 +#define OPUS_GET_LOOKAHEAD_REQUEST 4027 +/* #define OPUS_RESET_STATE 4028 */ +#define OPUS_GET_SAMPLE_RATE_REQUEST 4029 +#define OPUS_GET_FINAL_RANGE_REQUEST 4031 +#define OPUS_GET_PITCH_REQUEST 4033 +#define OPUS_SET_GAIN_REQUEST 4034 +#define OPUS_GET_GAIN_REQUEST 4045 /* Should have been 4035 */ +#define OPUS_SET_LSB_DEPTH_REQUEST 4036 +#define OPUS_GET_LSB_DEPTH_REQUEST 4037 +#define OPUS_GET_LAST_PACKET_DURATION_REQUEST 4039 +#define OPUS_SET_EXPERT_FRAME_DURATION_REQUEST 4040 +#define OPUS_GET_EXPERT_FRAME_DURATION_REQUEST 4041 +#define OPUS_SET_PREDICTION_DISABLED_REQUEST 4042 +#define OPUS_GET_PREDICTION_DISABLED_REQUEST 4043 +/* Don't use 4045, it's already taken by OPUS_GET_GAIN_REQUEST */ +#define OPUS_SET_PHASE_INVERSION_DISABLED_REQUEST 4046 +#define OPUS_GET_PHASE_INVERSION_DISABLED_REQUEST 4047 + +/** Defines for the presence of extended APIs. */ +#define OPUS_HAVE_OPUS_PROJECTION_H + +/* Macros to trigger compilation errors when the wrong types are provided to a CTL */ +#define __opus_check_int(x) (((void)((x) == (opus_int32)0)), (opus_int32)(x)) +#define __opus_check_int_ptr(ptr) ((ptr) + ((ptr) - (opus_int32*)(ptr))) +#define __opus_check_uint_ptr(ptr) ((ptr) + ((ptr) - (opus_uint32*)(ptr))) +#define __opus_check_val16_ptr(ptr) ((ptr) + ((ptr) - (opus_val16*)(ptr))) +/** @endcond */ + +/** @defgroup opus_ctlvalues Pre-defined values for CTL interface + * @see opus_genericctls, opus_encoderctls + * @{ + */ +/* Values for the various encoder CTLs */ +#define OPUS_AUTO -1000 /**opus_int32: Allowed values: 0-10, inclusive. + * + * @hideinitializer */ +#define OPUS_SET_COMPLEXITY(x) OPUS_SET_COMPLEXITY_REQUEST, __opus_check_int(x) +/** Gets the encoder's complexity configuration. + * @see OPUS_SET_COMPLEXITY + * @param[out] x opus_int32 *: Returns a value in the range 0-10, + * inclusive. + * @hideinitializer */ +#define OPUS_GET_COMPLEXITY(x) OPUS_GET_COMPLEXITY_REQUEST, __opus_check_int_ptr(x) + +/** Configures the bitrate in the encoder. + * Rates from 500 to 512000 bits per second are meaningful, as well as the + * special values #OPUS_AUTO and #OPUS_BITRATE_MAX. + * The value #OPUS_BITRATE_MAX can be used to cause the codec to use as much + * rate as it can, which is useful for controlling the rate by adjusting the + * output buffer size. + * @see OPUS_GET_BITRATE + * @param[in] x opus_int32: Bitrate in bits per second. The default + * is determined based on the number of + * channels and the input sampling rate. + * @hideinitializer */ +#define OPUS_SET_BITRATE(x) OPUS_SET_BITRATE_REQUEST, __opus_check_int(x) +/** Gets the encoder's bitrate configuration. + * @see OPUS_SET_BITRATE + * @param[out] x opus_int32 *: Returns the bitrate in bits per second. + * The default is determined based on the + * number of channels and the input + * sampling rate. + * @hideinitializer */ +#define OPUS_GET_BITRATE(x) OPUS_GET_BITRATE_REQUEST, __opus_check_int_ptr(x) + +/** Enables or disables variable bitrate (VBR) in the encoder. + * The configured bitrate may not be met exactly because frames must + * be an integer number of bytes in length. + * @see OPUS_GET_VBR + * @see OPUS_SET_VBR_CONSTRAINT + * @param[in] x opus_int32: Allowed values: + *
+ *
0
Hard CBR. For LPC/hybrid modes at very low bit-rate, this can + * cause noticeable quality degradation.
+ *
1
VBR (default). The exact type of VBR is controlled by + * #OPUS_SET_VBR_CONSTRAINT.
+ *
+ * @hideinitializer */ +#define OPUS_SET_VBR(x) OPUS_SET_VBR_REQUEST, __opus_check_int(x) +/** Determine if variable bitrate (VBR) is enabled in the encoder. + * @see OPUS_SET_VBR + * @see OPUS_GET_VBR_CONSTRAINT + * @param[out] x opus_int32 *: Returns one of the following values: + *
+ *
0
Hard CBR.
+ *
1
VBR (default). The exact type of VBR may be retrieved via + * #OPUS_GET_VBR_CONSTRAINT.
+ *
+ * @hideinitializer */ +#define OPUS_GET_VBR(x) OPUS_GET_VBR_REQUEST, __opus_check_int_ptr(x) + +/** Enables or disables constrained VBR in the encoder. + * This setting is ignored when the encoder is in CBR mode. + * @warning Only the MDCT mode of Opus currently heeds the constraint. + * Speech mode ignores it completely, hybrid mode may fail to obey it + * if the LPC layer uses more bitrate than the constraint would have + * permitted. + * @see OPUS_GET_VBR_CONSTRAINT + * @see OPUS_SET_VBR + * @param[in] x opus_int32: Allowed values: + *
+ *
0
Unconstrained VBR.
+ *
1
Constrained VBR (default). This creates a maximum of one + * frame of buffering delay assuming a transport with a + * serialization speed of the nominal bitrate.
+ *
+ * @hideinitializer */ +#define OPUS_SET_VBR_CONSTRAINT(x) OPUS_SET_VBR_CONSTRAINT_REQUEST, __opus_check_int(x) +/** Determine if constrained VBR is enabled in the encoder. + * @see OPUS_SET_VBR_CONSTRAINT + * @see OPUS_GET_VBR + * @param[out] x opus_int32 *: Returns one of the following values: + *
+ *
0
Unconstrained VBR.
+ *
1
Constrained VBR (default).
+ *
+ * @hideinitializer */ +#define OPUS_GET_VBR_CONSTRAINT(x) OPUS_GET_VBR_CONSTRAINT_REQUEST, __opus_check_int_ptr(x) + +/** Configures mono/stereo forcing in the encoder. + * This can force the encoder to produce packets encoded as either mono or + * stereo, regardless of the format of the input audio. This is useful when + * the caller knows that the input signal is currently a mono source embedded + * in a stereo stream. + * @see OPUS_GET_FORCE_CHANNELS + * @param[in] x opus_int32: Allowed values: + *
+ *
#OPUS_AUTO
Not forced (default)
+ *
1
Forced mono
+ *
2
Forced stereo
+ *
+ * @hideinitializer */ +#define OPUS_SET_FORCE_CHANNELS(x) OPUS_SET_FORCE_CHANNELS_REQUEST, __opus_check_int(x) +/** Gets the encoder's forced channel configuration. + * @see OPUS_SET_FORCE_CHANNELS + * @param[out] x opus_int32 *: + *
+ *
#OPUS_AUTO
Not forced (default)
+ *
1
Forced mono
+ *
2
Forced stereo
+ *
+ * @hideinitializer */ +#define OPUS_GET_FORCE_CHANNELS(x) OPUS_GET_FORCE_CHANNELS_REQUEST, __opus_check_int_ptr(x) + +/** Configures the maximum bandpass that the encoder will select automatically. + * Applications should normally use this instead of #OPUS_SET_BANDWIDTH + * (leaving that set to the default, #OPUS_AUTO). This allows the + * application to set an upper bound based on the type of input it is + * providing, but still gives the encoder the freedom to reduce the bandpass + * when the bitrate becomes too low, for better overall quality. + * @see OPUS_GET_MAX_BANDWIDTH + * @param[in] x opus_int32: Allowed values: + *
+ *
OPUS_BANDWIDTH_NARROWBAND
4 kHz passband
+ *
OPUS_BANDWIDTH_MEDIUMBAND
6 kHz passband
+ *
OPUS_BANDWIDTH_WIDEBAND
8 kHz passband
+ *
OPUS_BANDWIDTH_SUPERWIDEBAND
12 kHz passband
+ *
OPUS_BANDWIDTH_FULLBAND
20 kHz passband (default)
+ *
+ * @hideinitializer */ +#define OPUS_SET_MAX_BANDWIDTH(x) OPUS_SET_MAX_BANDWIDTH_REQUEST, __opus_check_int(x) + +/** Gets the encoder's configured maximum allowed bandpass. + * @see OPUS_SET_MAX_BANDWIDTH + * @param[out] x opus_int32 *: Allowed values: + *
+ *
#OPUS_BANDWIDTH_NARROWBAND
4 kHz passband
+ *
#OPUS_BANDWIDTH_MEDIUMBAND
6 kHz passband
+ *
#OPUS_BANDWIDTH_WIDEBAND
8 kHz passband
+ *
#OPUS_BANDWIDTH_SUPERWIDEBAND
12 kHz passband
+ *
#OPUS_BANDWIDTH_FULLBAND
20 kHz passband (default)
+ *
+ * @hideinitializer */ +#define OPUS_GET_MAX_BANDWIDTH(x) OPUS_GET_MAX_BANDWIDTH_REQUEST, __opus_check_int_ptr(x) + +/** Sets the encoder's bandpass to a specific value. + * This prevents the encoder from automatically selecting the bandpass based + * on the available bitrate. If an application knows the bandpass of the input + * audio it is providing, it should normally use #OPUS_SET_MAX_BANDWIDTH + * instead, which still gives the encoder the freedom to reduce the bandpass + * when the bitrate becomes too low, for better overall quality. + * @see OPUS_GET_BANDWIDTH + * @param[in] x opus_int32: Allowed values: + *
+ *
#OPUS_AUTO
(default)
+ *
#OPUS_BANDWIDTH_NARROWBAND
4 kHz passband
+ *
#OPUS_BANDWIDTH_MEDIUMBAND
6 kHz passband
+ *
#OPUS_BANDWIDTH_WIDEBAND
8 kHz passband
+ *
#OPUS_BANDWIDTH_SUPERWIDEBAND
12 kHz passband
+ *
#OPUS_BANDWIDTH_FULLBAND
20 kHz passband
+ *
+ * @hideinitializer */ +#define OPUS_SET_BANDWIDTH(x) OPUS_SET_BANDWIDTH_REQUEST, __opus_check_int(x) + +/** Configures the type of signal being encoded. + * This is a hint which helps the encoder's mode selection. + * @see OPUS_GET_SIGNAL + * @param[in] x opus_int32: Allowed values: + *
+ *
#OPUS_AUTO
(default)
+ *
#OPUS_SIGNAL_VOICE
Bias thresholds towards choosing LPC or Hybrid modes.
+ *
#OPUS_SIGNAL_MUSIC
Bias thresholds towards choosing MDCT modes.
+ *
+ * @hideinitializer */ +#define OPUS_SET_SIGNAL(x) OPUS_SET_SIGNAL_REQUEST, __opus_check_int(x) +/** Gets the encoder's configured signal type. + * @see OPUS_SET_SIGNAL + * @param[out] x opus_int32 *: Returns one of the following values: + *
+ *
#OPUS_AUTO
(default)
+ *
#OPUS_SIGNAL_VOICE
Bias thresholds towards choosing LPC or Hybrid modes.
+ *
#OPUS_SIGNAL_MUSIC
Bias thresholds towards choosing MDCT modes.
+ *
+ * @hideinitializer */ +#define OPUS_GET_SIGNAL(x) OPUS_GET_SIGNAL_REQUEST, __opus_check_int_ptr(x) + + +/** Configures the encoder's intended application. + * The initial value is a mandatory argument to the encoder_create function. + * @see OPUS_GET_APPLICATION + * @param[in] x opus_int32: Returns one of the following values: + *
+ *
#OPUS_APPLICATION_VOIP
+ *
Process signal for improved speech intelligibility.
+ *
#OPUS_APPLICATION_AUDIO
+ *
Favor faithfulness to the original input.
+ *
#OPUS_APPLICATION_RESTRICTED_LOWDELAY
+ *
Configure the minimum possible coding delay by disabling certain modes + * of operation.
+ *
+ * @hideinitializer */ +#define OPUS_SET_APPLICATION(x) OPUS_SET_APPLICATION_REQUEST, __opus_check_int(x) +/** Gets the encoder's configured application. + * @see OPUS_SET_APPLICATION + * @param[out] x opus_int32 *: Returns one of the following values: + *
+ *
#OPUS_APPLICATION_VOIP
+ *
Process signal for improved speech intelligibility.
+ *
#OPUS_APPLICATION_AUDIO
+ *
Favor faithfulness to the original input.
+ *
#OPUS_APPLICATION_RESTRICTED_LOWDELAY
+ *
Configure the minimum possible coding delay by disabling certain modes + * of operation.
+ *
+ * @hideinitializer */ +#define OPUS_GET_APPLICATION(x) OPUS_GET_APPLICATION_REQUEST, __opus_check_int_ptr(x) + +/** Gets the total samples of delay added by the entire codec. + * This can be queried by the encoder and then the provided number of samples can be + * skipped on from the start of the decoder's output to provide time aligned input + * and output. From the perspective of a decoding application the real data begins this many + * samples late. + * + * The decoder contribution to this delay is identical for all decoders, but the + * encoder portion of the delay may vary from implementation to implementation, + * version to version, or even depend on the encoder's initial configuration. + * Applications needing delay compensation should call this CTL rather than + * hard-coding a value. + * @param[out] x opus_int32 *: Number of lookahead samples + * @hideinitializer */ +#define OPUS_GET_LOOKAHEAD(x) OPUS_GET_LOOKAHEAD_REQUEST, __opus_check_int_ptr(x) + +/** Configures the encoder's use of inband forward error correction (FEC). + * @note This is only applicable to the LPC layer + * @see OPUS_GET_INBAND_FEC + * @param[in] x opus_int32: Allowed values: + *
+ *
0
Disable inband FEC (default).
+ *
1
Enable inband FEC.
+ *
+ * @hideinitializer */ +#define OPUS_SET_INBAND_FEC(x) OPUS_SET_INBAND_FEC_REQUEST, __opus_check_int(x) +/** Gets encoder's configured use of inband forward error correction. + * @see OPUS_SET_INBAND_FEC + * @param[out] x opus_int32 *: Returns one of the following values: + *
+ *
0
Inband FEC disabled (default).
+ *
1
Inband FEC enabled.
+ *
+ * @hideinitializer */ +#define OPUS_GET_INBAND_FEC(x) OPUS_GET_INBAND_FEC_REQUEST, __opus_check_int_ptr(x) + +/** Configures the encoder's expected packet loss percentage. + * Higher values trigger progressively more loss resistant behavior in the encoder + * at the expense of quality at a given bitrate in the absence of packet loss, but + * greater quality under loss. + * @see OPUS_GET_PACKET_LOSS_PERC + * @param[in] x opus_int32: Loss percentage in the range 0-100, inclusive (default: 0). + * @hideinitializer */ +#define OPUS_SET_PACKET_LOSS_PERC(x) OPUS_SET_PACKET_LOSS_PERC_REQUEST, __opus_check_int(x) +/** Gets the encoder's configured packet loss percentage. + * @see OPUS_SET_PACKET_LOSS_PERC + * @param[out] x opus_int32 *: Returns the configured loss percentage + * in the range 0-100, inclusive (default: 0). + * @hideinitializer */ +#define OPUS_GET_PACKET_LOSS_PERC(x) OPUS_GET_PACKET_LOSS_PERC_REQUEST, __opus_check_int_ptr(x) + +/** Configures the encoder's use of discontinuous transmission (DTX). + * @note This is only applicable to the LPC layer + * @see OPUS_GET_DTX + * @param[in] x opus_int32: Allowed values: + *
+ *
0
Disable DTX (default).
+ *
1
Enabled DTX.
+ *
+ * @hideinitializer */ +#define OPUS_SET_DTX(x) OPUS_SET_DTX_REQUEST, __opus_check_int(x) +/** Gets encoder's configured use of discontinuous transmission. + * @see OPUS_SET_DTX + * @param[out] x opus_int32 *: Returns one of the following values: + *
+ *
0
DTX disabled (default).
+ *
1
DTX enabled.
+ *
+ * @hideinitializer */ +#define OPUS_GET_DTX(x) OPUS_GET_DTX_REQUEST, __opus_check_int_ptr(x) +/** Configures the depth of signal being encoded. + * + * This is a hint which helps the encoder identify silence and near-silence. + * It represents the number of significant bits of linear intensity below + * which the signal contains ignorable quantization or other noise. + * + * For example, OPUS_SET_LSB_DEPTH(14) would be an appropriate setting + * for G.711 u-law input. OPUS_SET_LSB_DEPTH(16) would be appropriate + * for 16-bit linear pcm input with opus_encode_float(). + * + * When using opus_encode() instead of opus_encode_float(), or when libopus + * is compiled for fixed-point, the encoder uses the minimum of the value + * set here and the value 16. + * + * @see OPUS_GET_LSB_DEPTH + * @param[in] x opus_int32: Input precision in bits, between 8 and 24 + * (default: 24). + * @hideinitializer */ +#define OPUS_SET_LSB_DEPTH(x) OPUS_SET_LSB_DEPTH_REQUEST, __opus_check_int(x) +/** Gets the encoder's configured signal depth. + * @see OPUS_SET_LSB_DEPTH + * @param[out] x opus_int32 *: Input precision in bits, between 8 and + * 24 (default: 24). + * @hideinitializer */ +#define OPUS_GET_LSB_DEPTH(x) OPUS_GET_LSB_DEPTH_REQUEST, __opus_check_int_ptr(x) + +/** Configures the encoder's use of variable duration frames. + * When variable duration is enabled, the encoder is free to use a shorter frame + * size than the one requested in the opus_encode*() call. + * It is then the user's responsibility + * to verify how much audio was encoded by checking the ToC byte of the encoded + * packet. The part of the audio that was not encoded needs to be resent to the + * encoder for the next call. Do not use this option unless you really + * know what you are doing. + * @see OPUS_GET_EXPERT_FRAME_DURATION + * @param[in] x opus_int32: Allowed values: + *
+ *
OPUS_FRAMESIZE_ARG
Select frame size from the argument (default).
+ *
OPUS_FRAMESIZE_2_5_MS
Use 2.5 ms frames.
+ *
OPUS_FRAMESIZE_5_MS
Use 5 ms frames.
+ *
OPUS_FRAMESIZE_10_MS
Use 10 ms frames.
+ *
OPUS_FRAMESIZE_20_MS
Use 20 ms frames.
+ *
OPUS_FRAMESIZE_40_MS
Use 40 ms frames.
+ *
OPUS_FRAMESIZE_60_MS
Use 60 ms frames.
+ *
OPUS_FRAMESIZE_80_MS
Use 80 ms frames.
+ *
OPUS_FRAMESIZE_100_MS
Use 100 ms frames.
+ *
OPUS_FRAMESIZE_120_MS
Use 120 ms frames.
+ *
+ * @hideinitializer */ +#define OPUS_SET_EXPERT_FRAME_DURATION(x) OPUS_SET_EXPERT_FRAME_DURATION_REQUEST, __opus_check_int(x) +/** Gets the encoder's configured use of variable duration frames. + * @see OPUS_SET_EXPERT_FRAME_DURATION + * @param[out] x opus_int32 *: Returns one of the following values: + *
+ *
OPUS_FRAMESIZE_ARG
Select frame size from the argument (default).
+ *
OPUS_FRAMESIZE_2_5_MS
Use 2.5 ms frames.
+ *
OPUS_FRAMESIZE_5_MS
Use 5 ms frames.
+ *
OPUS_FRAMESIZE_10_MS
Use 10 ms frames.
+ *
OPUS_FRAMESIZE_20_MS
Use 20 ms frames.
+ *
OPUS_FRAMESIZE_40_MS
Use 40 ms frames.
+ *
OPUS_FRAMESIZE_60_MS
Use 60 ms frames.
+ *
OPUS_FRAMESIZE_80_MS
Use 80 ms frames.
+ *
OPUS_FRAMESIZE_100_MS
Use 100 ms frames.
+ *
OPUS_FRAMESIZE_120_MS
Use 120 ms frames.
+ *
+ * @hideinitializer */ +#define OPUS_GET_EXPERT_FRAME_DURATION(x) OPUS_GET_EXPERT_FRAME_DURATION_REQUEST, __opus_check_int_ptr(x) + +/** If set to 1, disables almost all use of prediction, making frames almost + * completely independent. This reduces quality. + * @see OPUS_GET_PREDICTION_DISABLED + * @param[in] x opus_int32: Allowed values: + *
+ *
0
Enable prediction (default).
+ *
1
Disable prediction.
+ *
+ * @hideinitializer */ +#define OPUS_SET_PREDICTION_DISABLED(x) OPUS_SET_PREDICTION_DISABLED_REQUEST, __opus_check_int(x) +/** Gets the encoder's configured prediction status. + * @see OPUS_SET_PREDICTION_DISABLED + * @param[out] x opus_int32 *: Returns one of the following values: + *
+ *
0
Prediction enabled (default).
+ *
1
Prediction disabled.
+ *
+ * @hideinitializer */ +#define OPUS_GET_PREDICTION_DISABLED(x) OPUS_GET_PREDICTION_DISABLED_REQUEST, __opus_check_int_ptr(x) + +/**@}*/ + +/** @defgroup opus_genericctls Generic CTLs + * + * These macros are used with the \c opus_decoder_ctl and + * \c opus_encoder_ctl calls to generate a particular + * request. + * + * When called on an \c OpusDecoder they apply to that + * particular decoder instance. When called on an + * \c OpusEncoder they apply to the corresponding setting + * on that encoder instance, if present. + * + * Some usage examples: + * + * @code + * int ret; + * opus_int32 pitch; + * ret = opus_decoder_ctl(dec_ctx, OPUS_GET_PITCH(&pitch)); + * if (ret == OPUS_OK) return ret; + * + * opus_encoder_ctl(enc_ctx, OPUS_RESET_STATE); + * opus_decoder_ctl(dec_ctx, OPUS_RESET_STATE); + * + * opus_int32 enc_bw, dec_bw; + * opus_encoder_ctl(enc_ctx, OPUS_GET_BANDWIDTH(&enc_bw)); + * opus_decoder_ctl(dec_ctx, OPUS_GET_BANDWIDTH(&dec_bw)); + * if (enc_bw != dec_bw) { + * printf("packet bandwidth mismatch!\n"); + * } + * @endcode + * + * @see opus_encoder, opus_decoder_ctl, opus_encoder_ctl, opus_decoderctls, opus_encoderctls + * @{ + */ + +/** Resets the codec state to be equivalent to a freshly initialized state. + * This should be called when switching streams in order to prevent + * the back to back decoding from giving different results from + * one at a time decoding. + * @hideinitializer */ +#define OPUS_RESET_STATE 4028 + +/** Gets the final state of the codec's entropy coder. + * This is used for testing purposes, + * The encoder and decoder state should be identical after coding a payload + * (assuming no data corruption or software bugs) + * + * @param[out] x opus_uint32 *: Entropy coder state + * + * @hideinitializer */ +#define OPUS_GET_FINAL_RANGE(x) OPUS_GET_FINAL_RANGE_REQUEST, __opus_check_uint_ptr(x) + +/** Gets the encoder's configured bandpass or the decoder's last bandpass. + * @see OPUS_SET_BANDWIDTH + * @param[out] x opus_int32 *: Returns one of the following values: + *
+ *
#OPUS_AUTO
(default)
+ *
#OPUS_BANDWIDTH_NARROWBAND
4 kHz passband
+ *
#OPUS_BANDWIDTH_MEDIUMBAND
6 kHz passband
+ *
#OPUS_BANDWIDTH_WIDEBAND
8 kHz passband
+ *
#OPUS_BANDWIDTH_SUPERWIDEBAND
12 kHz passband
+ *
#OPUS_BANDWIDTH_FULLBAND
20 kHz passband
+ *
+ * @hideinitializer */ +#define OPUS_GET_BANDWIDTH(x) OPUS_GET_BANDWIDTH_REQUEST, __opus_check_int_ptr(x) + +/** Gets the sampling rate the encoder or decoder was initialized with. + * This simply returns the Fs value passed to opus_encoder_init() + * or opus_decoder_init(). + * @param[out] x opus_int32 *: Sampling rate of encoder or decoder. + * @hideinitializer + */ +#define OPUS_GET_SAMPLE_RATE(x) OPUS_GET_SAMPLE_RATE_REQUEST, __opus_check_int_ptr(x) + +/** If set to 1, disables the use of phase inversion for intensity stereo, + * improving the quality of mono downmixes, but slightly reducing normal + * stereo quality. Disabling phase inversion in the decoder does not comply + * with RFC 6716, although it does not cause any interoperability issue and + * is expected to become part of the Opus standard once RFC 6716 is updated + * by draft-ietf-codec-opus-update. + * @see OPUS_GET_PHASE_INVERSION_DISABLED + * @param[in] x opus_int32: Allowed values: + *
+ *
0
Enable phase inversion (default).
+ *
1
Disable phase inversion.
+ *
+ * @hideinitializer */ +#define OPUS_SET_PHASE_INVERSION_DISABLED(x) OPUS_SET_PHASE_INVERSION_DISABLED_REQUEST, __opus_check_int(x) +/** Gets the encoder's configured phase inversion status. + * @see OPUS_SET_PHASE_INVERSION_DISABLED + * @param[out] x opus_int32 *: Returns one of the following values: + *
+ *
0
Stereo phase inversion enabled (default).
+ *
1
Stereo phase inversion disabled.
+ *
+ * @hideinitializer */ +#define OPUS_GET_PHASE_INVERSION_DISABLED(x) OPUS_GET_PHASE_INVERSION_DISABLED_REQUEST, __opus_check_int_ptr(x) + +/**@}*/ + +/** @defgroup opus_decoderctls Decoder related CTLs + * @see opus_genericctls, opus_encoderctls, opus_decoder + * @{ + */ + +/** Configures decoder gain adjustment. + * Scales the decoded output by a factor specified in Q8 dB units. + * This has a maximum range of -32768 to 32767 inclusive, and returns + * OPUS_BAD_ARG otherwise. The default is zero indicating no adjustment. + * This setting survives decoder reset. + * + * gain = pow(10, x/(20.0*256)) + * + * @param[in] x opus_int32: Amount to scale PCM signal by in Q8 dB units. + * @hideinitializer */ +#define OPUS_SET_GAIN(x) OPUS_SET_GAIN_REQUEST, __opus_check_int(x) +/** Gets the decoder's configured gain adjustment. @see OPUS_SET_GAIN + * + * @param[out] x opus_int32 *: Amount to scale PCM signal by in Q8 dB units. + * @hideinitializer */ +#define OPUS_GET_GAIN(x) OPUS_GET_GAIN_REQUEST, __opus_check_int_ptr(x) + +/** Gets the duration (in samples) of the last packet successfully decoded or concealed. + * @param[out] x opus_int32 *: Number of samples (at current sampling rate). + * @hideinitializer */ +#define OPUS_GET_LAST_PACKET_DURATION(x) OPUS_GET_LAST_PACKET_DURATION_REQUEST, __opus_check_int_ptr(x) + +/** Gets the pitch of the last decoded frame, if available. + * This can be used for any post-processing algorithm requiring the use of pitch, + * e.g. time stretching/shortening. If the last frame was not voiced, or if the + * pitch was not coded in the frame, then zero is returned. + * + * This CTL is only implemented for decoder instances. + * + * @param[out] x opus_int32 *: pitch period at 48 kHz (or 0 if not available) + * + * @hideinitializer */ +#define OPUS_GET_PITCH(x) OPUS_GET_PITCH_REQUEST, __opus_check_int_ptr(x) + +/**@}*/ + +/** @defgroup opus_libinfo Opus library information functions + * @{ + */ + +/** Converts an opus error code into a human readable string. + * + * @param[in] error int: Error number + * @returns Error string + */ +OPUS_EXPORT const char *opus_strerror(int error); + +/** Gets the libopus version string. + * + * Applications may look for the substring "-fixed" in the version string to + * determine whether they have a fixed-point or floating-point build at + * runtime. + * + * @returns Version string + */ +OPUS_EXPORT const char *opus_get_version_string(void); +/**@}*/ + +#ifdef __cplusplus +} +#endif + +#endif /* OPUS_DEFINES_H */ diff --git a/library/opusencoder/src/main/cpp/opus/include/opus_multistream.h b/library/opusencoder/src/main/cpp/opus/include/opus_multistream.h new file mode 100644 index 0000000000..babcee6905 --- /dev/null +++ b/library/opusencoder/src/main/cpp/opus/include/opus_multistream.h @@ -0,0 +1,660 @@ +/* Copyright (c) 2011 Xiph.Org Foundation + Written by Jean-Marc Valin */ +/* + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + - Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + - Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER + OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +/** + * @file opus_multistream.h + * @brief Opus reference implementation multistream API + */ + +#ifndef OPUS_MULTISTREAM_H +#define OPUS_MULTISTREAM_H + +#include "opus.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/** @cond OPUS_INTERNAL_DOC */ + +/** Macros to trigger compilation errors when the wrong types are provided to a + * CTL. */ +/**@{*/ +#define __opus_check_encstate_ptr(ptr) ((ptr) + ((ptr) - (OpusEncoder**)(ptr))) +#define __opus_check_decstate_ptr(ptr) ((ptr) + ((ptr) - (OpusDecoder**)(ptr))) +/**@}*/ + +/** These are the actual encoder and decoder CTL ID numbers. + * They should not be used directly by applications. + * In general, SETs should be even and GETs should be odd.*/ +/**@{*/ +#define OPUS_MULTISTREAM_GET_ENCODER_STATE_REQUEST 5120 +#define OPUS_MULTISTREAM_GET_DECODER_STATE_REQUEST 5122 +/**@}*/ + +/** @endcond */ + +/** @defgroup opus_multistream_ctls Multistream specific encoder and decoder CTLs + * + * These are convenience macros that are specific to the + * opus_multistream_encoder_ctl() and opus_multistream_decoder_ctl() + * interface. + * The CTLs from @ref opus_genericctls, @ref opus_encoderctls, and + * @ref opus_decoderctls may be applied to a multistream encoder or decoder as + * well. + * In addition, you may retrieve the encoder or decoder state for an specific + * stream via #OPUS_MULTISTREAM_GET_ENCODER_STATE or + * #OPUS_MULTISTREAM_GET_DECODER_STATE and apply CTLs to it individually. + */ +/**@{*/ + +/** Gets the encoder state for an individual stream of a multistream encoder. + * @param[in] x opus_int32: The index of the stream whose encoder you + * wish to retrieve. + * This must be non-negative and less than + * the streams parameter used + * to initialize the encoder. + * @param[out] y OpusEncoder**: Returns a pointer to the given + * encoder state. + * @retval OPUS_BAD_ARG The index of the requested stream was out of range. + * @hideinitializer + */ +#define OPUS_MULTISTREAM_GET_ENCODER_STATE(x,y) OPUS_MULTISTREAM_GET_ENCODER_STATE_REQUEST, __opus_check_int(x), __opus_check_encstate_ptr(y) + +/** Gets the decoder state for an individual stream of a multistream decoder. + * @param[in] x opus_int32: The index of the stream whose decoder you + * wish to retrieve. + * This must be non-negative and less than + * the streams parameter used + * to initialize the decoder. + * @param[out] y OpusDecoder**: Returns a pointer to the given + * decoder state. + * @retval OPUS_BAD_ARG The index of the requested stream was out of range. + * @hideinitializer + */ +#define OPUS_MULTISTREAM_GET_DECODER_STATE(x,y) OPUS_MULTISTREAM_GET_DECODER_STATE_REQUEST, __opus_check_int(x), __opus_check_decstate_ptr(y) + +/**@}*/ + +/** @defgroup opus_multistream Opus Multistream API + * @{ + * + * The multistream API allows individual Opus streams to be combined into a + * single packet, enabling support for up to 255 channels. Unlike an + * elementary Opus stream, the encoder and decoder must negotiate the channel + * configuration before the decoder can successfully interpret the data in the + * packets produced by the encoder. Some basic information, such as packet + * duration, can be computed without any special negotiation. + * + * The format for multistream Opus packets is defined in + * RFC 7845 + * and is based on the self-delimited Opus framing described in Appendix B of + * RFC 6716. + * Normal Opus packets are just a degenerate case of multistream Opus packets, + * and can be encoded or decoded with the multistream API by setting + * streams to 1 when initializing the encoder or + * decoder. + * + * Multistream Opus streams can contain up to 255 elementary Opus streams. + * These may be either "uncoupled" or "coupled", indicating that the decoder + * is configured to decode them to either 1 or 2 channels, respectively. + * The streams are ordered so that all coupled streams appear at the + * beginning. + * + * A mapping table defines which decoded channel i + * should be used for each input/output (I/O) channel j. This table is + * typically provided as an unsigned char array. + * Let i = mapping[j] be the index for I/O channel j. + * If i < 2*coupled_streams, then I/O channel j is + * encoded as the left channel of stream (i/2) if i + * is even, or as the right channel of stream (i/2) if + * i is odd. Otherwise, I/O channel j is encoded as + * mono in stream (i - coupled_streams), unless it has the special + * value 255, in which case it is omitted from the encoding entirely (the + * decoder will reproduce it as silence). Each value i must either + * be the special value 255 or be less than streams + coupled_streams. + * + * The output channels specified by the encoder + * should use the + * Vorbis + * channel ordering. A decoder may wish to apply an additional permutation + * to the mapping the encoder used to achieve a different output channel + * order (e.g. for outputing in WAV order). + * + * Each multistream packet contains an Opus packet for each stream, and all of + * the Opus packets in a single multistream packet must have the same + * duration. Therefore the duration of a multistream packet can be extracted + * from the TOC sequence of the first stream, which is located at the + * beginning of the packet, just like an elementary Opus stream: + * + * @code + * int nb_samples; + * int nb_frames; + * nb_frames = opus_packet_get_nb_frames(data, len); + * if (nb_frames < 1) + * return nb_frames; + * nb_samples = opus_packet_get_samples_per_frame(data, 48000) * nb_frames; + * @endcode + * + * The general encoding and decoding process proceeds exactly the same as in + * the normal @ref opus_encoder and @ref opus_decoder APIs. + * See their documentation for an overview of how to use the corresponding + * multistream functions. + */ + +/** Opus multistream encoder state. + * This contains the complete state of a multistream Opus encoder. + * It is position independent and can be freely copied. + * @see opus_multistream_encoder_create + * @see opus_multistream_encoder_init + */ +typedef struct OpusMSEncoder OpusMSEncoder; + +/** Opus multistream decoder state. + * This contains the complete state of a multistream Opus decoder. + * It is position independent and can be freely copied. + * @see opus_multistream_decoder_create + * @see opus_multistream_decoder_init + */ +typedef struct OpusMSDecoder OpusMSDecoder; + +/**\name Multistream encoder functions */ +/**@{*/ + +/** Gets the size of an OpusMSEncoder structure. + * @param streams int: The total number of streams to encode from the + * input. + * This must be no more than 255. + * @param coupled_streams int: Number of coupled (2 channel) streams + * to encode. + * This must be no larger than the total + * number of streams. + * Additionally, The total number of + * encoded channels (streams + + * coupled_streams) must be no + * more than 255. + * @returns The size in bytes on success, or a negative error code + * (see @ref opus_errorcodes) on error. + */ +OPUS_EXPORT OPUS_WARN_UNUSED_RESULT opus_int32 opus_multistream_encoder_get_size( + int streams, + int coupled_streams +); + +OPUS_EXPORT OPUS_WARN_UNUSED_RESULT opus_int32 opus_multistream_surround_encoder_get_size( + int channels, + int mapping_family +); + + +/** Allocates and initializes a multistream encoder state. + * Call opus_multistream_encoder_destroy() to release + * this object when finished. + * @param Fs opus_int32: Sampling rate of the input signal (in Hz). + * This must be one of 8000, 12000, 16000, + * 24000, or 48000. + * @param channels int: Number of channels in the input signal. + * This must be at most 255. + * It may be greater than the number of + * coded channels (streams + + * coupled_streams). + * @param streams int: The total number of streams to encode from the + * input. + * This must be no more than the number of channels. + * @param coupled_streams int: Number of coupled (2 channel) streams + * to encode. + * This must be no larger than the total + * number of streams. + * Additionally, The total number of + * encoded channels (streams + + * coupled_streams) must be no + * more than the number of input channels. + * @param[in] mapping const unsigned char[channels]: Mapping from + * encoded channels to input channels, as described in + * @ref opus_multistream. As an extra constraint, the + * multistream encoder does not allow encoding coupled + * streams for which one channel is unused since this + * is never a good idea. + * @param application int: The target encoder application. + * This must be one of the following: + *
+ *
#OPUS_APPLICATION_VOIP
+ *
Process signal for improved speech intelligibility.
+ *
#OPUS_APPLICATION_AUDIO
+ *
Favor faithfulness to the original input.
+ *
#OPUS_APPLICATION_RESTRICTED_LOWDELAY
+ *
Configure the minimum possible coding delay by disabling certain modes + * of operation.
+ *
+ * @param[out] error int *: Returns #OPUS_OK on success, or an error + * code (see @ref opus_errorcodes) on + * failure. + */ +OPUS_EXPORT OPUS_WARN_UNUSED_RESULT OpusMSEncoder *opus_multistream_encoder_create( + opus_int32 Fs, + int channels, + int streams, + int coupled_streams, + const unsigned char *mapping, + int application, + int *error +) OPUS_ARG_NONNULL(5); + +OPUS_EXPORT OPUS_WARN_UNUSED_RESULT OpusMSEncoder *opus_multistream_surround_encoder_create( + opus_int32 Fs, + int channels, + int mapping_family, + int *streams, + int *coupled_streams, + unsigned char *mapping, + int application, + int *error +) OPUS_ARG_NONNULL(4) OPUS_ARG_NONNULL(5) OPUS_ARG_NONNULL(6); + +/** Initialize a previously allocated multistream encoder state. + * The memory pointed to by \a st must be at least the size returned by + * opus_multistream_encoder_get_size(). + * This is intended for applications which use their own allocator instead of + * malloc. + * To reset a previously initialized state, use the #OPUS_RESET_STATE CTL. + * @see opus_multistream_encoder_create + * @see opus_multistream_encoder_get_size + * @param st OpusMSEncoder*: Multistream encoder state to initialize. + * @param Fs opus_int32: Sampling rate of the input signal (in Hz). + * This must be one of 8000, 12000, 16000, + * 24000, or 48000. + * @param channels int: Number of channels in the input signal. + * This must be at most 255. + * It may be greater than the number of + * coded channels (streams + + * coupled_streams). + * @param streams int: The total number of streams to encode from the + * input. + * This must be no more than the number of channels. + * @param coupled_streams int: Number of coupled (2 channel) streams + * to encode. + * This must be no larger than the total + * number of streams. + * Additionally, The total number of + * encoded channels (streams + + * coupled_streams) must be no + * more than the number of input channels. + * @param[in] mapping const unsigned char[channels]: Mapping from + * encoded channels to input channels, as described in + * @ref opus_multistream. As an extra constraint, the + * multistream encoder does not allow encoding coupled + * streams for which one channel is unused since this + * is never a good idea. + * @param application int: The target encoder application. + * This must be one of the following: + *
+ *
#OPUS_APPLICATION_VOIP
+ *
Process signal for improved speech intelligibility.
+ *
#OPUS_APPLICATION_AUDIO
+ *
Favor faithfulness to the original input.
+ *
#OPUS_APPLICATION_RESTRICTED_LOWDELAY
+ *
Configure the minimum possible coding delay by disabling certain modes + * of operation.
+ *
+ * @returns #OPUS_OK on success, or an error code (see @ref opus_errorcodes) + * on failure. + */ +OPUS_EXPORT int opus_multistream_encoder_init( + OpusMSEncoder *st, + opus_int32 Fs, + int channels, + int streams, + int coupled_streams, + const unsigned char *mapping, + int application +) OPUS_ARG_NONNULL(1) OPUS_ARG_NONNULL(6); + +OPUS_EXPORT int opus_multistream_surround_encoder_init( + OpusMSEncoder *st, + opus_int32 Fs, + int channels, + int mapping_family, + int *streams, + int *coupled_streams, + unsigned char *mapping, + int application +) OPUS_ARG_NONNULL(1) OPUS_ARG_NONNULL(5) OPUS_ARG_NONNULL(6) OPUS_ARG_NONNULL(7); + +/** Encodes a multistream Opus frame. + * @param st OpusMSEncoder*: Multistream encoder state. + * @param[in] pcm const opus_int16*: The input signal as interleaved + * samples. + * This must contain + * frame_size*channels + * samples. + * @param frame_size int: Number of samples per channel in the input + * signal. + * This must be an Opus frame size for the + * encoder's sampling rate. + * For example, at 48 kHz the permitted values + * are 120, 240, 480, 960, 1920, and 2880. + * Passing in a duration of less than 10 ms + * (480 samples at 48 kHz) will prevent the + * encoder from using the LPC or hybrid modes. + * @param[out] data unsigned char*: Output payload. + * This must contain storage for at + * least \a max_data_bytes. + * @param [in] max_data_bytes opus_int32: Size of the allocated + * memory for the output + * payload. This may be + * used to impose an upper limit on + * the instant bitrate, but should + * not be used as the only bitrate + * control. Use #OPUS_SET_BITRATE to + * control the bitrate. + * @returns The length of the encoded packet (in bytes) on success or a + * negative error code (see @ref opus_errorcodes) on failure. + */ +OPUS_EXPORT OPUS_WARN_UNUSED_RESULT int opus_multistream_encode( + OpusMSEncoder *st, + const opus_int16 *pcm, + int frame_size, + unsigned char *data, + opus_int32 max_data_bytes +) OPUS_ARG_NONNULL(1) OPUS_ARG_NONNULL(2) OPUS_ARG_NONNULL(4); + +/** Encodes a multistream Opus frame from floating point input. + * @param st OpusMSEncoder*: Multistream encoder state. + * @param[in] pcm const float*: The input signal as interleaved + * samples with a normal range of + * +/-1.0. + * Samples with a range beyond +/-1.0 + * are supported but will be clipped by + * decoders using the integer API and + * should only be used if it is known + * that the far end supports extended + * dynamic range. + * This must contain + * frame_size*channels + * samples. + * @param frame_size int: Number of samples per channel in the input + * signal. + * This must be an Opus frame size for the + * encoder's sampling rate. + * For example, at 48 kHz the permitted values + * are 120, 240, 480, 960, 1920, and 2880. + * Passing in a duration of less than 10 ms + * (480 samples at 48 kHz) will prevent the + * encoder from using the LPC or hybrid modes. + * @param[out] data unsigned char*: Output payload. + * This must contain storage for at + * least \a max_data_bytes. + * @param [in] max_data_bytes opus_int32: Size of the allocated + * memory for the output + * payload. This may be + * used to impose an upper limit on + * the instant bitrate, but should + * not be used as the only bitrate + * control. Use #OPUS_SET_BITRATE to + * control the bitrate. + * @returns The length of the encoded packet (in bytes) on success or a + * negative error code (see @ref opus_errorcodes) on failure. + */ +OPUS_EXPORT OPUS_WARN_UNUSED_RESULT int opus_multistream_encode_float( + OpusMSEncoder *st, + const float *pcm, + int frame_size, + unsigned char *data, + opus_int32 max_data_bytes +) OPUS_ARG_NONNULL(1) OPUS_ARG_NONNULL(2) OPUS_ARG_NONNULL(4); + +/** Frees an OpusMSEncoder allocated by + * opus_multistream_encoder_create(). + * @param st OpusMSEncoder*: Multistream encoder state to be freed. + */ +OPUS_EXPORT void opus_multistream_encoder_destroy(OpusMSEncoder *st); + +/** Perform a CTL function on a multistream Opus encoder. + * + * Generally the request and subsequent arguments are generated by a + * convenience macro. + * @param st OpusMSEncoder*: Multistream encoder state. + * @param request This and all remaining parameters should be replaced by one + * of the convenience macros in @ref opus_genericctls, + * @ref opus_encoderctls, or @ref opus_multistream_ctls. + * @see opus_genericctls + * @see opus_encoderctls + * @see opus_multistream_ctls + */ +OPUS_EXPORT int opus_multistream_encoder_ctl(OpusMSEncoder *st, int request, ...) OPUS_ARG_NONNULL(1); + +/**@}*/ + +/**\name Multistream decoder functions */ +/**@{*/ + +/** Gets the size of an OpusMSDecoder structure. + * @param streams int: The total number of streams coded in the + * input. + * This must be no more than 255. + * @param coupled_streams int: Number streams to decode as coupled + * (2 channel) streams. + * This must be no larger than the total + * number of streams. + * Additionally, The total number of + * coded channels (streams + + * coupled_streams) must be no + * more than 255. + * @returns The size in bytes on success, or a negative error code + * (see @ref opus_errorcodes) on error. + */ +OPUS_EXPORT OPUS_WARN_UNUSED_RESULT opus_int32 opus_multistream_decoder_get_size( + int streams, + int coupled_streams +); + +/** Allocates and initializes a multistream decoder state. + * Call opus_multistream_decoder_destroy() to release + * this object when finished. + * @param Fs opus_int32: Sampling rate to decode at (in Hz). + * This must be one of 8000, 12000, 16000, + * 24000, or 48000. + * @param channels int: Number of channels to output. + * This must be at most 255. + * It may be different from the number of coded + * channels (streams + + * coupled_streams). + * @param streams int: The total number of streams coded in the + * input. + * This must be no more than 255. + * @param coupled_streams int: Number of streams to decode as coupled + * (2 channel) streams. + * This must be no larger than the total + * number of streams. + * Additionally, The total number of + * coded channels (streams + + * coupled_streams) must be no + * more than 255. + * @param[in] mapping const unsigned char[channels]: Mapping from + * coded channels to output channels, as described in + * @ref opus_multistream. + * @param[out] error int *: Returns #OPUS_OK on success, or an error + * code (see @ref opus_errorcodes) on + * failure. + */ +OPUS_EXPORT OPUS_WARN_UNUSED_RESULT OpusMSDecoder *opus_multistream_decoder_create( + opus_int32 Fs, + int channels, + int streams, + int coupled_streams, + const unsigned char *mapping, + int *error +) OPUS_ARG_NONNULL(5); + +/** Intialize a previously allocated decoder state object. + * The memory pointed to by \a st must be at least the size returned by + * opus_multistream_encoder_get_size(). + * This is intended for applications which use their own allocator instead of + * malloc. + * To reset a previously initialized state, use the #OPUS_RESET_STATE CTL. + * @see opus_multistream_decoder_create + * @see opus_multistream_deocder_get_size + * @param st OpusMSEncoder*: Multistream encoder state to initialize. + * @param Fs opus_int32: Sampling rate to decode at (in Hz). + * This must be one of 8000, 12000, 16000, + * 24000, or 48000. + * @param channels int: Number of channels to output. + * This must be at most 255. + * It may be different from the number of coded + * channels (streams + + * coupled_streams). + * @param streams int: The total number of streams coded in the + * input. + * This must be no more than 255. + * @param coupled_streams int: Number of streams to decode as coupled + * (2 channel) streams. + * This must be no larger than the total + * number of streams. + * Additionally, The total number of + * coded channels (streams + + * coupled_streams) must be no + * more than 255. + * @param[in] mapping const unsigned char[channels]: Mapping from + * coded channels to output channels, as described in + * @ref opus_multistream. + * @returns #OPUS_OK on success, or an error code (see @ref opus_errorcodes) + * on failure. + */ +OPUS_EXPORT int opus_multistream_decoder_init( + OpusMSDecoder *st, + opus_int32 Fs, + int channels, + int streams, + int coupled_streams, + const unsigned char *mapping +) OPUS_ARG_NONNULL(1) OPUS_ARG_NONNULL(6); + +/** Decode a multistream Opus packet. + * @param st OpusMSDecoder*: Multistream decoder state. + * @param[in] data const unsigned char*: Input payload. + * Use a NULL + * pointer to indicate packet + * loss. + * @param len opus_int32: Number of bytes in payload. + * @param[out] pcm opus_int16*: Output signal, with interleaved + * samples. + * This must contain room for + * frame_size*channels + * samples. + * @param frame_size int: The number of samples per channel of + * available space in \a pcm. + * If this is less than the maximum packet duration + * (120 ms; 5760 for 48kHz), this function will not be capable + * of decoding some packets. In the case of PLC (data==NULL) + * or FEC (decode_fec=1), then frame_size needs to be exactly + * the duration of audio that is missing, otherwise the + * decoder will not be in the optimal state to decode the + * next incoming packet. For the PLC and FEC cases, frame_size + * must be a multiple of 2.5 ms. + * @param decode_fec int: Flag (0 or 1) to request that any in-band + * forward error correction data be decoded. + * If no such data is available, the frame is + * decoded as if it were lost. + * @returns Number of samples decoded on success or a negative error code + * (see @ref opus_errorcodes) on failure. + */ +OPUS_EXPORT OPUS_WARN_UNUSED_RESULT int opus_multistream_decode( + OpusMSDecoder *st, + const unsigned char *data, + opus_int32 len, + opus_int16 *pcm, + int frame_size, + int decode_fec +) OPUS_ARG_NONNULL(1) OPUS_ARG_NONNULL(4); + +/** Decode a multistream Opus packet with floating point output. + * @param st OpusMSDecoder*: Multistream decoder state. + * @param[in] data const unsigned char*: Input payload. + * Use a NULL + * pointer to indicate packet + * loss. + * @param len opus_int32: Number of bytes in payload. + * @param[out] pcm opus_int16*: Output signal, with interleaved + * samples. + * This must contain room for + * frame_size*channels + * samples. + * @param frame_size int: The number of samples per channel of + * available space in \a pcm. + * If this is less than the maximum packet duration + * (120 ms; 5760 for 48kHz), this function will not be capable + * of decoding some packets. In the case of PLC (data==NULL) + * or FEC (decode_fec=1), then frame_size needs to be exactly + * the duration of audio that is missing, otherwise the + * decoder will not be in the optimal state to decode the + * next incoming packet. For the PLC and FEC cases, frame_size + * must be a multiple of 2.5 ms. + * @param decode_fec int: Flag (0 or 1) to request that any in-band + * forward error correction data be decoded. + * If no such data is available, the frame is + * decoded as if it were lost. + * @returns Number of samples decoded on success or a negative error code + * (see @ref opus_errorcodes) on failure. + */ +OPUS_EXPORT OPUS_WARN_UNUSED_RESULT int opus_multistream_decode_float( + OpusMSDecoder *st, + const unsigned char *data, + opus_int32 len, + float *pcm, + int frame_size, + int decode_fec +) OPUS_ARG_NONNULL(1) OPUS_ARG_NONNULL(4); + +/** Perform a CTL function on a multistream Opus decoder. + * + * Generally the request and subsequent arguments are generated by a + * convenience macro. + * @param st OpusMSDecoder*: Multistream decoder state. + * @param request This and all remaining parameters should be replaced by one + * of the convenience macros in @ref opus_genericctls, + * @ref opus_decoderctls, or @ref opus_multistream_ctls. + * @see opus_genericctls + * @see opus_decoderctls + * @see opus_multistream_ctls + */ +OPUS_EXPORT int opus_multistream_decoder_ctl(OpusMSDecoder *st, int request, ...) OPUS_ARG_NONNULL(1); + +/** Frees an OpusMSDecoder allocated by + * opus_multistream_decoder_create(). + * @param st OpusMSDecoder: Multistream decoder state to be freed. + */ +OPUS_EXPORT void opus_multistream_decoder_destroy(OpusMSDecoder *st); + +/**@}*/ + +/**@}*/ + +#ifdef __cplusplus +} +#endif + +#endif /* OPUS_MULTISTREAM_H */ diff --git a/library/opusencoder/src/main/cpp/opus/include/opus_projection.h b/library/opusencoder/src/main/cpp/opus/include/opus_projection.h new file mode 100644 index 0000000000..9dabf4e85c --- /dev/null +++ b/library/opusencoder/src/main/cpp/opus/include/opus_projection.h @@ -0,0 +1,568 @@ +/* Copyright (c) 2017 Google Inc. + Written by Andrew Allen */ +/* + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + - Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + - Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER + OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +/** + * @file opus_projection.h + * @brief Opus projection reference API + */ + +#ifndef OPUS_PROJECTION_H +#define OPUS_PROJECTION_H + +#include "opus_multistream.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/** @cond OPUS_INTERNAL_DOC */ + +/** These are the actual encoder and decoder CTL ID numbers. + * They should not be used directly by applications.c + * In general, SETs should be even and GETs should be odd.*/ +/**@{*/ +#define OPUS_PROJECTION_GET_DEMIXING_MATRIX_GAIN_REQUEST 6001 +#define OPUS_PROJECTION_GET_DEMIXING_MATRIX_SIZE_REQUEST 6003 +#define OPUS_PROJECTION_GET_DEMIXING_MATRIX_REQUEST 6005 +/**@}*/ + + +/** @endcond */ + +/** @defgroup opus_projection_ctls Projection specific encoder and decoder CTLs + * + * These are convenience macros that are specific to the + * opus_projection_encoder_ctl() and opus_projection_decoder_ctl() + * interface. + * The CTLs from @ref opus_genericctls, @ref opus_encoderctls, + * @ref opus_decoderctls, and @ref opus_multistream_ctls may be applied to a + * projection encoder or decoder as well. + */ +/**@{*/ + +/** Gets the gain (in dB. S7.8-format) of the demixing matrix from the encoder. + * @param[out] x opus_int32 *: Returns the gain (in dB. S7.8-format) + * of the demixing matrix. + * @hideinitializer + */ +#define OPUS_PROJECTION_GET_DEMIXING_MATRIX_GAIN(x) OPUS_PROJECTION_GET_DEMIXING_MATRIX_GAIN_REQUEST, __opus_check_int_ptr(x) + + +/** Gets the size in bytes of the demixing matrix from the encoder. + * @param[out] x opus_int32 *: Returns the size in bytes of the + * demixing matrix. + * @hideinitializer + */ +#define OPUS_PROJECTION_GET_DEMIXING_MATRIX_SIZE(x) OPUS_PROJECTION_GET_DEMIXING_MATRIX_SIZE_REQUEST, __opus_check_int_ptr(x) + + +/** Copies the demixing matrix to the supplied pointer location. + * @param[out] x unsigned char *: Returns the demixing matrix to the + * supplied pointer location. + * @param y opus_int32: The size in bytes of the reserved memory at the + * pointer location. + * @hideinitializer + */ +#define OPUS_PROJECTION_GET_DEMIXING_MATRIX(x,y) OPUS_PROJECTION_GET_DEMIXING_MATRIX_REQUEST, x, __opus_check_int(y) + + +/**@}*/ + +/** Opus projection encoder state. + * This contains the complete state of a projection Opus encoder. + * It is position independent and can be freely copied. + * @see opus_projection_ambisonics_encoder_create + */ +typedef struct OpusProjectionEncoder OpusProjectionEncoder; + + +/** Opus projection decoder state. + * This contains the complete state of a projection Opus decoder. + * It is position independent and can be freely copied. + * @see opus_projection_decoder_create + * @see opus_projection_decoder_init + */ +typedef struct OpusProjectionDecoder OpusProjectionDecoder; + + +/**\name Projection encoder functions */ +/**@{*/ + +/** Gets the size of an OpusProjectionEncoder structure. + * @param channels int: The total number of input channels to encode. + * This must be no more than 255. + * @param mapping_family int: The mapping family to use for selecting + * the appropriate projection. + * @returns The size in bytes on success, or a negative error code + * (see @ref opus_errorcodes) on error. + */ +OPUS_EXPORT OPUS_WARN_UNUSED_RESULT opus_int32 opus_projection_ambisonics_encoder_get_size( + int channels, + int mapping_family +); + + +/** Allocates and initializes a projection encoder state. + * Call opus_projection_encoder_destroy() to release + * this object when finished. + * @param Fs opus_int32: Sampling rate of the input signal (in Hz). + * This must be one of 8000, 12000, 16000, + * 24000, or 48000. + * @param channels int: Number of channels in the input signal. + * This must be at most 255. + * It may be greater than the number of + * coded channels (streams + + * coupled_streams). + * @param mapping_family int: The mapping family to use for selecting + * the appropriate projection. + * @param[out] streams int *: The total number of streams that will + * be encoded from the input. + * @param[out] coupled_streams int *: Number of coupled (2 channel) + * streams that will be encoded from the input. + * @param application int: The target encoder application. + * This must be one of the following: + *
+ *
#OPUS_APPLICATION_VOIP
+ *
Process signal for improved speech intelligibility.
+ *
#OPUS_APPLICATION_AUDIO
+ *
Favor faithfulness to the original input.
+ *
#OPUS_APPLICATION_RESTRICTED_LOWDELAY
+ *
Configure the minimum possible coding delay by disabling certain modes + * of operation.
+ *
+ * @param[out] error int *: Returns #OPUS_OK on success, or an error + * code (see @ref opus_errorcodes) on + * failure. + */ +OPUS_EXPORT OPUS_WARN_UNUSED_RESULT OpusProjectionEncoder *opus_projection_ambisonics_encoder_create( + opus_int32 Fs, + int channels, + int mapping_family, + int *streams, + int *coupled_streams, + int application, + int *error +) OPUS_ARG_NONNULL(4) OPUS_ARG_NONNULL(5); + + +/** Initialize a previously allocated projection encoder state. + * The memory pointed to by \a st must be at least the size returned by + * opus_projection_ambisonics_encoder_get_size(). + * This is intended for applications which use their own allocator instead of + * malloc. + * To reset a previously initialized state, use the #OPUS_RESET_STATE CTL. + * @see opus_projection_ambisonics_encoder_create + * @see opus_projection_ambisonics_encoder_get_size + * @param st OpusProjectionEncoder*: Projection encoder state to initialize. + * @param Fs opus_int32: Sampling rate of the input signal (in Hz). + * This must be one of 8000, 12000, 16000, + * 24000, or 48000. + * @param channels int: Number of channels in the input signal. + * This must be at most 255. + * It may be greater than the number of + * coded channels (streams + + * coupled_streams). + * @param streams int: The total number of streams to encode from the + * input. + * This must be no more than the number of channels. + * @param coupled_streams int: Number of coupled (2 channel) streams + * to encode. + * This must be no larger than the total + * number of streams. + * Additionally, The total number of + * encoded channels (streams + + * coupled_streams) must be no + * more than the number of input channels. + * @param application int: The target encoder application. + * This must be one of the following: + *
+ *
#OPUS_APPLICATION_VOIP
+ *
Process signal for improved speech intelligibility.
+ *
#OPUS_APPLICATION_AUDIO
+ *
Favor faithfulness to the original input.
+ *
#OPUS_APPLICATION_RESTRICTED_LOWDELAY
+ *
Configure the minimum possible coding delay by disabling certain modes + * of operation.
+ *
+ * @returns #OPUS_OK on success, or an error code (see @ref opus_errorcodes) + * on failure. + */ +OPUS_EXPORT int opus_projection_ambisonics_encoder_init( + OpusProjectionEncoder *st, + opus_int32 Fs, + int channels, + int mapping_family, + int *streams, + int *coupled_streams, + int application +) OPUS_ARG_NONNULL(1) OPUS_ARG_NONNULL(5) OPUS_ARG_NONNULL(6); + + +/** Encodes a projection Opus frame. + * @param st OpusProjectionEncoder*: Projection encoder state. + * @param[in] pcm const opus_int16*: The input signal as interleaved + * samples. + * This must contain + * frame_size*channels + * samples. + * @param frame_size int: Number of samples per channel in the input + * signal. + * This must be an Opus frame size for the + * encoder's sampling rate. + * For example, at 48 kHz the permitted values + * are 120, 240, 480, 960, 1920, and 2880. + * Passing in a duration of less than 10 ms + * (480 samples at 48 kHz) will prevent the + * encoder from using the LPC or hybrid modes. + * @param[out] data unsigned char*: Output payload. + * This must contain storage for at + * least \a max_data_bytes. + * @param [in] max_data_bytes opus_int32: Size of the allocated + * memory for the output + * payload. This may be + * used to impose an upper limit on + * the instant bitrate, but should + * not be used as the only bitrate + * control. Use #OPUS_SET_BITRATE to + * control the bitrate. + * @returns The length of the encoded packet (in bytes) on success or a + * negative error code (see @ref opus_errorcodes) on failure. + */ +OPUS_EXPORT OPUS_WARN_UNUSED_RESULT int opus_projection_encode( + OpusProjectionEncoder *st, + const opus_int16 *pcm, + int frame_size, + unsigned char *data, + opus_int32 max_data_bytes +) OPUS_ARG_NONNULL(1) OPUS_ARG_NONNULL(2) OPUS_ARG_NONNULL(4); + + +/** Encodes a projection Opus frame from floating point input. + * @param st OpusProjectionEncoder*: Projection encoder state. + * @param[in] pcm const float*: The input signal as interleaved + * samples with a normal range of + * +/-1.0. + * Samples with a range beyond +/-1.0 + * are supported but will be clipped by + * decoders using the integer API and + * should only be used if it is known + * that the far end supports extended + * dynamic range. + * This must contain + * frame_size*channels + * samples. + * @param frame_size int: Number of samples per channel in the input + * signal. + * This must be an Opus frame size for the + * encoder's sampling rate. + * For example, at 48 kHz the permitted values + * are 120, 240, 480, 960, 1920, and 2880. + * Passing in a duration of less than 10 ms + * (480 samples at 48 kHz) will prevent the + * encoder from using the LPC or hybrid modes. + * @param[out] data unsigned char*: Output payload. + * This must contain storage for at + * least \a max_data_bytes. + * @param [in] max_data_bytes opus_int32: Size of the allocated + * memory for the output + * payload. This may be + * used to impose an upper limit on + * the instant bitrate, but should + * not be used as the only bitrate + * control. Use #OPUS_SET_BITRATE to + * control the bitrate. + * @returns The length of the encoded packet (in bytes) on success or a + * negative error code (see @ref opus_errorcodes) on failure. + */ +OPUS_EXPORT OPUS_WARN_UNUSED_RESULT int opus_projection_encode_float( + OpusProjectionEncoder *st, + const float *pcm, + int frame_size, + unsigned char *data, + opus_int32 max_data_bytes +) OPUS_ARG_NONNULL(1) OPUS_ARG_NONNULL(2) OPUS_ARG_NONNULL(4); + + +/** Frees an OpusProjectionEncoder allocated by + * opus_projection_ambisonics_encoder_create(). + * @param st OpusProjectionEncoder*: Projection encoder state to be freed. + */ +OPUS_EXPORT void opus_projection_encoder_destroy(OpusProjectionEncoder *st); + + +/** Perform a CTL function on a projection Opus encoder. + * + * Generally the request and subsequent arguments are generated by a + * convenience macro. + * @param st OpusProjectionEncoder*: Projection encoder state. + * @param request This and all remaining parameters should be replaced by one + * of the convenience macros in @ref opus_genericctls, + * @ref opus_encoderctls, @ref opus_multistream_ctls, or + * @ref opus_projection_ctls + * @see opus_genericctls + * @see opus_encoderctls + * @see opus_multistream_ctls + * @see opus_projection_ctls + */ +OPUS_EXPORT int opus_projection_encoder_ctl(OpusProjectionEncoder *st, int request, ...) OPUS_ARG_NONNULL(1); + + +/**@}*/ + +/**\name Projection decoder functions */ +/**@{*/ + +/** Gets the size of an OpusProjectionDecoder structure. + * @param channels int: The total number of output channels. + * This must be no more than 255. + * @param streams int: The total number of streams coded in the + * input. + * This must be no more than 255. + * @param coupled_streams int: Number streams to decode as coupled + * (2 channel) streams. + * This must be no larger than the total + * number of streams. + * Additionally, The total number of + * coded channels (streams + + * coupled_streams) must be no + * more than 255. + * @returns The size in bytes on success, or a negative error code + * (see @ref opus_errorcodes) on error. + */ +OPUS_EXPORT OPUS_WARN_UNUSED_RESULT opus_int32 opus_projection_decoder_get_size( + int channels, + int streams, + int coupled_streams +); + + +/** Allocates and initializes a projection decoder state. + * Call opus_projection_decoder_destroy() to release + * this object when finished. + * @param Fs opus_int32: Sampling rate to decode at (in Hz). + * This must be one of 8000, 12000, 16000, + * 24000, or 48000. + * @param channels int: Number of channels to output. + * This must be at most 255. + * It may be different from the number of coded + * channels (streams + + * coupled_streams). + * @param streams int: The total number of streams coded in the + * input. + * This must be no more than 255. + * @param coupled_streams int: Number of streams to decode as coupled + * (2 channel) streams. + * This must be no larger than the total + * number of streams. + * Additionally, The total number of + * coded channels (streams + + * coupled_streams) must be no + * more than 255. + * @param[in] demixing_matrix const unsigned char[demixing_matrix_size]: Demixing matrix + * that mapping from coded channels to output channels, + * as described in @ref opus_projection and + * @ref opus_projection_ctls. + * @param demixing_matrix_size opus_int32: The size in bytes of the + * demixing matrix, as + * described in @ref + * opus_projection_ctls. + * @param[out] error int *: Returns #OPUS_OK on success, or an error + * code (see @ref opus_errorcodes) on + * failure. + */ +OPUS_EXPORT OPUS_WARN_UNUSED_RESULT OpusProjectionDecoder *opus_projection_decoder_create( + opus_int32 Fs, + int channels, + int streams, + int coupled_streams, + unsigned char *demixing_matrix, + opus_int32 demixing_matrix_size, + int *error +) OPUS_ARG_NONNULL(5); + + +/** Intialize a previously allocated projection decoder state object. + * The memory pointed to by \a st must be at least the size returned by + * opus_projection_decoder_get_size(). + * This is intended for applications which use their own allocator instead of + * malloc. + * To reset a previously initialized state, use the #OPUS_RESET_STATE CTL. + * @see opus_projection_decoder_create + * @see opus_projection_deocder_get_size + * @param st OpusProjectionDecoder*: Projection encoder state to initialize. + * @param Fs opus_int32: Sampling rate to decode at (in Hz). + * This must be one of 8000, 12000, 16000, + * 24000, or 48000. + * @param channels int: Number of channels to output. + * This must be at most 255. + * It may be different from the number of coded + * channels (streams + + * coupled_streams). + * @param streams int: The total number of streams coded in the + * input. + * This must be no more than 255. + * @param coupled_streams int: Number of streams to decode as coupled + * (2 channel) streams. + * This must be no larger than the total + * number of streams. + * Additionally, The total number of + * coded channels (streams + + * coupled_streams) must be no + * more than 255. + * @param[in] demixing_matrix const unsigned char[demixing_matrix_size]: Demixing matrix + * that mapping from coded channels to output channels, + * as described in @ref opus_projection and + * @ref opus_projection_ctls. + * @param demixing_matrix_size opus_int32: The size in bytes of the + * demixing matrix, as + * described in @ref + * opus_projection_ctls. + * @returns #OPUS_OK on success, or an error code (see @ref opus_errorcodes) + * on failure. + */ +OPUS_EXPORT int opus_projection_decoder_init( + OpusProjectionDecoder *st, + opus_int32 Fs, + int channels, + int streams, + int coupled_streams, + unsigned char *demixing_matrix, + opus_int32 demixing_matrix_size +) OPUS_ARG_NONNULL(1) OPUS_ARG_NONNULL(6); + + +/** Decode a projection Opus packet. + * @param st OpusProjectionDecoder*: Projection decoder state. + * @param[in] data const unsigned char*: Input payload. + * Use a NULL + * pointer to indicate packet + * loss. + * @param len opus_int32: Number of bytes in payload. + * @param[out] pcm opus_int16*: Output signal, with interleaved + * samples. + * This must contain room for + * frame_size*channels + * samples. + * @param frame_size int: The number of samples per channel of + * available space in \a pcm. + * If this is less than the maximum packet duration + * (120 ms; 5760 for 48kHz), this function will not be capable + * of decoding some packets. In the case of PLC (data==NULL) + * or FEC (decode_fec=1), then frame_size needs to be exactly + * the duration of audio that is missing, otherwise the + * decoder will not be in the optimal state to decode the + * next incoming packet. For the PLC and FEC cases, frame_size + * must be a multiple of 2.5 ms. + * @param decode_fec int: Flag (0 or 1) to request that any in-band + * forward error correction data be decoded. + * If no such data is available, the frame is + * decoded as if it were lost. + * @returns Number of samples decoded on success or a negative error code + * (see @ref opus_errorcodes) on failure. + */ +OPUS_EXPORT OPUS_WARN_UNUSED_RESULT int opus_projection_decode( + OpusProjectionDecoder *st, + const unsigned char *data, + opus_int32 len, + opus_int16 *pcm, + int frame_size, + int decode_fec +) OPUS_ARG_NONNULL(1) OPUS_ARG_NONNULL(4); + + +/** Decode a projection Opus packet with floating point output. + * @param st OpusProjectionDecoder*: Projection decoder state. + * @param[in] data const unsigned char*: Input payload. + * Use a NULL + * pointer to indicate packet + * loss. + * @param len opus_int32: Number of bytes in payload. + * @param[out] pcm opus_int16*: Output signal, with interleaved + * samples. + * This must contain room for + * frame_size*channels + * samples. + * @param frame_size int: The number of samples per channel of + * available space in \a pcm. + * If this is less than the maximum packet duration + * (120 ms; 5760 for 48kHz), this function will not be capable + * of decoding some packets. In the case of PLC (data==NULL) + * or FEC (decode_fec=1), then frame_size needs to be exactly + * the duration of audio that is missing, otherwise the + * decoder will not be in the optimal state to decode the + * next incoming packet. For the PLC and FEC cases, frame_size + * must be a multiple of 2.5 ms. + * @param decode_fec int: Flag (0 or 1) to request that any in-band + * forward error correction data be decoded. + * If no such data is available, the frame is + * decoded as if it were lost. + * @returns Number of samples decoded on success or a negative error code + * (see @ref opus_errorcodes) on failure. + */ +OPUS_EXPORT OPUS_WARN_UNUSED_RESULT int opus_projection_decode_float( + OpusProjectionDecoder *st, + const unsigned char *data, + opus_int32 len, + float *pcm, + int frame_size, + int decode_fec +) OPUS_ARG_NONNULL(1) OPUS_ARG_NONNULL(4); + + +/** Perform a CTL function on a projection Opus decoder. + * + * Generally the request and subsequent arguments are generated by a + * convenience macro. + * @param st OpusProjectionDecoder*: Projection decoder state. + * @param request This and all remaining parameters should be replaced by one + * of the convenience macros in @ref opus_genericctls, + * @ref opus_decoderctls, @ref opus_multistream_ctls, or + * @ref opus_projection_ctls. + * @see opus_genericctls + * @see opus_decoderctls + * @see opus_multistream_ctls + * @see opus_projection_ctls + */ +OPUS_EXPORT int opus_projection_decoder_ctl(OpusProjectionDecoder *st, int request, ...) OPUS_ARG_NONNULL(1); + + +/** Frees an OpusProjectionDecoder allocated by + * opus_projection_decoder_create(). + * @param st OpusProjectionDecoder: Projection decoder state to be freed. + */ +OPUS_EXPORT void opus_projection_decoder_destroy(OpusProjectionDecoder *st); + + +/**@}*/ + +/**@}*/ + +#ifdef __cplusplus +} +#endif + +#endif /* OPUS_PROJECTION_H */ diff --git a/library/opusencoder/src/main/cpp/opus/include/opus_types.h b/library/opusencoder/src/main/cpp/opus/include/opus_types.h new file mode 100644 index 0000000000..7cf675580f --- /dev/null +++ b/library/opusencoder/src/main/cpp/opus/include/opus_types.h @@ -0,0 +1,166 @@ +/* (C) COPYRIGHT 1994-2002 Xiph.Org Foundation */ +/* Modified by Jean-Marc Valin */ +/* + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + - Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + - Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER + OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ +/* opus_types.h based on ogg_types.h from libogg */ + +/** + @file opus_types.h + @brief Opus reference implementation types +*/ +#ifndef OPUS_TYPES_H +#define OPUS_TYPES_H + +#define opus_int int /* used for counters etc; at least 16 bits */ +#define opus_int64 long long +#define opus_int8 signed char + +#define opus_uint unsigned int /* used for counters etc; at least 16 bits */ +#define opus_uint64 unsigned long long +#define opus_uint8 unsigned char + +/* Use the real stdint.h if it's there (taken from Paul Hsieh's pstdint.h) */ +#if (defined(__STDC__) && __STDC__ && defined(__STDC_VERSION__) && __STDC_VERSION__ >= 199901L) || (defined(__GNUC__) && (defined(_STDINT_H) || defined(_STDINT_H_)) || defined (HAVE_STDINT_H)) +#include +# undef opus_int64 +# undef opus_int8 +# undef opus_uint64 +# undef opus_uint8 + typedef int8_t opus_int8; + typedef uint8_t opus_uint8; + typedef int16_t opus_int16; + typedef uint16_t opus_uint16; + typedef int32_t opus_int32; + typedef uint32_t opus_uint32; + typedef int64_t opus_int64; + typedef uint64_t opus_uint64; +#elif defined(_WIN32) + +# if defined(__CYGWIN__) +# include <_G_config.h> + typedef _G_int32_t opus_int32; + typedef _G_uint32_t opus_uint32; + typedef _G_int16 opus_int16; + typedef _G_uint16 opus_uint16; +# elif defined(__MINGW32__) + typedef short opus_int16; + typedef unsigned short opus_uint16; + typedef int opus_int32; + typedef unsigned int opus_uint32; +# elif defined(__MWERKS__) + typedef int opus_int32; + typedef unsigned int opus_uint32; + typedef short opus_int16; + typedef unsigned short opus_uint16; +# else + /* MSVC/Borland */ + typedef __int32 opus_int32; + typedef unsigned __int32 opus_uint32; + typedef __int16 opus_int16; + typedef unsigned __int16 opus_uint16; +# endif + +#elif defined(__MACOS__) + +# include + typedef SInt16 opus_int16; + typedef UInt16 opus_uint16; + typedef SInt32 opus_int32; + typedef UInt32 opus_uint32; + +#elif (defined(__APPLE__) && defined(__MACH__)) /* MacOS X Framework build */ + +# include + typedef int16_t opus_int16; + typedef u_int16_t opus_uint16; + typedef int32_t opus_int32; + typedef u_int32_t opus_uint32; + +#elif defined(__BEOS__) + + /* Be */ +# include + typedef int16 opus_int16; + typedef u_int16 opus_uint16; + typedef int32_t opus_int32; + typedef u_int32_t opus_uint32; + +#elif defined (__EMX__) + + /* OS/2 GCC */ + typedef short opus_int16; + typedef unsigned short opus_uint16; + typedef int opus_int32; + typedef unsigned int opus_uint32; + +#elif defined (DJGPP) + + /* DJGPP */ + typedef short opus_int16; + typedef unsigned short opus_uint16; + typedef int opus_int32; + typedef unsigned int opus_uint32; + +#elif defined(R5900) + + /* PS2 EE */ + typedef int opus_int32; + typedef unsigned opus_uint32; + typedef short opus_int16; + typedef unsigned short opus_uint16; + +#elif defined(__SYMBIAN32__) + + /* Symbian GCC */ + typedef signed short opus_int16; + typedef unsigned short opus_uint16; + typedef signed int opus_int32; + typedef unsigned int opus_uint32; + +#elif defined(CONFIG_TI_C54X) || defined (CONFIG_TI_C55X) + + typedef short opus_int16; + typedef unsigned short opus_uint16; + typedef long opus_int32; + typedef unsigned long opus_uint32; + +#elif defined(CONFIG_TI_C6X) + + typedef short opus_int16; + typedef unsigned short opus_uint16; + typedef int opus_int32; + typedef unsigned int opus_uint32; + +#else + + /* Give up, take a reasonable guess */ + typedef short opus_int16; + typedef unsigned short opus_uint16; + typedef int opus_int32; + typedef unsigned int opus_uint32; + +#endif + +#endif /* OPUS_TYPES_H */ diff --git a/library/opusencoder/src/main/cpp/opus/include/opusenc.h b/library/opusencoder/src/main/cpp/opus/include/opusenc.h new file mode 100644 index 0000000000..ca1a5efe8e --- /dev/null +++ b/library/opusencoder/src/main/cpp/opus/include/opusenc.h @@ -0,0 +1,358 @@ +/* Copyright (c) 2017 Jean-Marc Valin */ +/* + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + - Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + - Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER + OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#if !defined(_opusenc_h) +# define _opusenc_h (1) + +/**\mainpage + \section Introduction + + This is the documentation for the libopusenc C API. + + The libopusenc package provides a convenient high-level API for + encoding Ogg Opus files. + + \section Organization + + The main API is divided into several sections: + - \ref encoding + - \ref comments + - \ref encoder_ctl + - \ref callbacks + - \ref error_codes + + \section Overview + + The libopusfile API provides an easy way to encode Ogg Opus files using + libopus. +*/ + +# if defined(__cplusplus) +extern "C" { +# endif + +#include + +#ifndef OPE_EXPORT +# if defined(WIN32) +# if defined(OPE_BUILD) && defined(DLL_EXPORT) +# define OPE_EXPORT __declspec(dllexport) +# else +# define OPE_EXPORT +# endif +# elif defined(__GNUC__) && defined(OPE_BUILD) +# define OPE_EXPORT __attribute__ ((visibility ("default"))) +# else +# define OPE_EXPORT +# endif +#endif + +/**\defgroup error_codes Error Codes*/ +/*@{*/ +/**\name List of possible error codes + Many of the functions in this library return a negative error code when a + function fails. + This list provides a brief explanation of the common errors. + See each individual function for more details on what a specific error code + means in that context.*/ +/*@{*/ + + +/* Bump this when we change the API. */ +/** API version for this header. Can be used to check for features at compile time. */ +#define OPE_API_VERSION 0 + +#define OPE_OK 0 +/* Based on the relevant libopus code minus 10. */ +#define OPE_BAD_ARG -11 +#define OPE_INTERNAL_ERROR -13 +#define OPE_UNIMPLEMENTED -15 +#define OPE_ALLOC_FAIL -17 + +/* Specific to libopusenc. */ +#define OPE_CANNOT_OPEN -30 +#define OPE_TOO_LATE -31 +#define OPE_UNRECOVERABLE -32 +#define OPE_INVALID_PICTURE -33 +#define OPE_INVALID_ICON -34 +/*@}*/ +/*@}*/ + + +/* These are the "raw" request values -- they should usually not be used. */ +#define OPE_SET_DECISION_DELAY_REQUEST 14000 +#define OPE_GET_DECISION_DELAY_REQUEST 14001 +#define OPE_SET_MUXING_DELAY_REQUEST 14002 +#define OPE_GET_MUXING_DELAY_REQUEST 14003 +#define OPE_SET_COMMENT_PADDING_REQUEST 14004 +#define OPE_GET_COMMENT_PADDING_REQUEST 14005 +#define OPE_SET_SERIALNO_REQUEST 14006 +#define OPE_GET_SERIALNO_REQUEST 14007 +#define OPE_SET_PACKET_CALLBACK_REQUEST 14008 +/*#define OPE_GET_PACKET_CALLBACK_REQUEST 14009*/ +#define OPE_SET_HEADER_GAIN_REQUEST 14010 +#define OPE_GET_HEADER_GAIN_REQUEST 14011 + +/**\defgroup encoder_ctl Encoding Options*/ +/*@{*/ + +/**\name Control parameters + + Macros for setting encoder options.*/ +/*@{*/ + +#define OPE_SET_DECISION_DELAY(x) OPE_SET_DECISION_DELAY_REQUEST, __opus_check_int(x) +#define OPE_GET_DECISION_DELAY(x) OPE_GET_DECISION_DELAY_REQUEST, __opus_check_int_ptr(x) +#define OPE_SET_MUXING_DELAY(x) OPE_SET_MUXING_DELAY_REQUEST, __opus_check_int(x) +#define OPE_GET_MUXING_DELAY(x) OPE_GET_MUXING_DELAY_REQUEST, __opus_check_int_ptr(x) +#define OPE_SET_COMMENT_PADDING(x) OPE_SET_COMMENT_PADDING_REQUEST, __opus_check_int(x) +#define OPE_GET_COMMENT_PADDING(x) OPE_GET_COMMENT_PADDING_REQUEST, __opus_check_int_ptr(x) +#define OPE_SET_SERIALNO(x) OPE_SET_SERIALNO_REQUEST, __opus_check_int(x) +#define OPE_GET_SERIALNO(x) OPE_GET_SERIALNO_REQUEST, __opus_check_int_ptr(x) +/* FIXME: Add type-checking macros to these. */ +#define OPE_SET_PACKET_CALLBACK(x,u) OPE_SET_PACKET_CALLBACK_REQUEST, (x), (u) +/*#define OPE_GET_PACKET_CALLBACK(x,u) OPE_GET_PACKET_CALLBACK_REQUEST, (x), (u)*/ +#define OPE_SET_HEADER_GAIN(x,u) OPE_SET_HEADER_GAIN_REQUEST, __opus_check_int(x) +#define OPE_GET_HEADER_GAIN(x,u) OPE_GET_HEADER_GAIN_REQUEST, __opus_check_int_ptr(x) +/*@}*/ +/*@}*/ + +/**\defgroup callbacks Callback Functions */ +/*@{*/ + +/**\name Callback functions + + These are the callbacks that can be implemented for an encoder.*/ +/*@{*/ + +/** Called for writing a page. */ +typedef int (*ope_write_func)(void *user_data, const unsigned char *ptr, opus_int32 len); + +/** Called for closing a stream. */ +typedef int (*ope_close_func)(void *user_data); + +/** Called on every packet encoded (including header). */ +typedef int (*ope_packet_func)(void *user_data, const unsigned char *packet_ptr, opus_int32 packet_len, opus_uint32 flags); + +/** Callback functions for accessing the stream. */ +typedef struct { + /** Callback for writing to the stream. */ + ope_write_func write; + /** Callback for closing the stream. */ + ope_close_func close; +} OpusEncCallbacks; +/*@}*/ +/*@}*/ + +/** Opaque comments struct. */ +typedef struct OggOpusComments OggOpusComments; + +/** Opaque encoder struct. */ +typedef struct OggOpusEnc OggOpusEnc; + +/**\defgroup comments Comments Handling */ +/*@{*/ + +/**\name Functions for handling comments + + These functions make it possible to add comments and pictures to Ogg Opus files.*/ +/*@{*/ + +/** Create a new comments object. + \return Newly-created comments object. */ +OPE_EXPORT OggOpusComments *ope_comments_create(void); + +/** Create a deep copy of a comments object. + \param comments Comments object to copy + \return Deep copy of input. */ +OPE_EXPORT OggOpusComments *ope_comments_copy(OggOpusComments *comments); + +/** Destroys a comments object. + \param comments Comments object to destroy*/ +OPE_EXPORT void ope_comments_destroy(OggOpusComments *comments); + +/** Add a comment. + \param[in,out] comments Where to add the comments + \param tag Tag for the comment (must not contain = char) + \param val Value for the tag + \return Error code + */ +OPE_EXPORT int ope_comments_add(OggOpusComments *comments, const char *tag, const char *val); + +/** Add a comment as a single tag=value string. + \param[in,out] comments Where to add the comments + \param tag_and_val string of the form tag=value (must contain = char) + \return Error code + */ +OPE_EXPORT int ope_comments_add_string(OggOpusComments *comments, const char *tag_and_val); + +/** Add a picture. + \param[in,out] comments Where to add the comments + \param filename File name for the picture + \param picture_type Type of picture (-1 for default) + \param description Description (NULL means no comment) + \return Error code + */ +OPE_EXPORT int ope_comments_add_picture(OggOpusComments *comments, const char *filename, int picture_type, const char *description); + +/*@}*/ +/*@}*/ + +/**\defgroup encoding Encoding */ +/*@{*/ + +/**\name Functions for encoding Ogg Opus files + + These functions make it possible to encode Ogg Opus files.*/ +/*@{*/ + +/** Create a new OggOpus file. + \param path Path where to create the file + \param comments Comments associated with the stream + \param rate Input sampling rate (48 kHz is faster) + \param channels Number of channels + \param family Mapping family (0 for mono/stereo, 1 for surround) + \param[out] error Error code (NULL if no error is to be returned) + \return Newly-created encoder. + */ +OPE_EXPORT OggOpusEnc *ope_encoder_create_file(const char *path, OggOpusComments *comments, opus_int32 rate, int channels, int family, int *error); + +/** Create a new OggOpus stream to be handled using callbacks + \param callbacks Callback functions + \param user_data Pointer to be associated with the stream and passed to the callbacks + \param comments Comments associated with the stream + \param rate Input sampling rate (48 kHz is faster) + \param channels Number of channels + \param family Mapping family (0 for mono/stereo, 1 for surround) + \param[out] error Error code (NULL if no error is to be returned) + \return Newly-created encoder. + */ +OPE_EXPORT OggOpusEnc *ope_encoder_create_callbacks(const OpusEncCallbacks *callbacks, void *user_data, + OggOpusComments *comments, opus_int32 rate, int channels, int family, int *error); + +/** Create a new OggOpus stream to be used along with.ope_encoder_get_page(). + This is mostly useful for muxing with other streams. + \param comments Comments associated with the stream + \param rate Input sampling rate (48 kHz is faster) + \param channels Number of channels + \param family Mapping family (0 for mono/stereo, 1 for surround) + \param[out] error Error code (NULL if no error is to be returned) + \return Newly-created encoder. + */ +OPE_EXPORT OggOpusEnc *ope_encoder_create_pull(OggOpusComments *comments, opus_int32 rate, int channels, int family, int *error); + +/** Add/encode any number of float samples to the stream. + \param[in,out] enc Encoder + \param pcm Floating-point PCM values in the +/-1 range (interleaved if multiple channels) + \param samples_per_channel Number of samples for each channel + \return Error code*/ +OPE_EXPORT int ope_encoder_write_float(OggOpusEnc *enc, const float *pcm, int samples_per_channel); + +/** Add/encode any number of 16-bit linear samples to the stream. + \param[in,out] enc Encoder + \param pcm Linear 16-bit PCM values in the [-32768,32767] range (interleaved if multiple channels) + \param samples_per_channel Number of samples for each channel + \return Error code*/ +OPE_EXPORT int ope_encoder_write(OggOpusEnc *enc, const opus_int16 *pcm, int samples_per_channel); + +/** Get the next page from the stream (only if using ope_encoder_create_pull()). + \param[in,out] enc Encoder + \param[out] page Next available encoded page + \param[out] len Size (in bytes) of the page returned + \param flush If non-zero, forces a flush of the page (if any data avaiable) + \return 1 if there is a page available, 0 if not. */ +OPE_EXPORT int ope_encoder_get_page(OggOpusEnc *enc, unsigned char **page, opus_int32 *len, int flush); + +/** Finalizes the stream, but does not deallocate the object. + \param[in,out] enc Encoder + \return Error code + */ +OPE_EXPORT int ope_encoder_drain(OggOpusEnc *enc); + +/** Deallocates the obect. Make sure to ope_drain() first. + \param[in,out] enc Encoder + */ +OPE_EXPORT void ope_encoder_destroy(OggOpusEnc *enc); + +/** Ends the stream and create a new stream within the same file. + \param[in,out] enc Encoder + \param comments Comments associated with the stream + \return Error code + */ +OPE_EXPORT int ope_encoder_chain_current(OggOpusEnc *enc, OggOpusComments *comments); + +/** Ends the stream and create a new file. + \param[in,out] enc Encoder + \param path Path where to write the new file + \param comments Comments associated with the stream + \return Error code + */ +OPE_EXPORT int ope_encoder_continue_new_file(OggOpusEnc *enc, const char *path, OggOpusComments *comments); + +/** Ends the stream and create a new file (callback-based). + \param[in,out] enc Encoder + \param user_data Pointer to be associated with the new stream and passed to the callbacks + \param comments Comments associated with the stream + \return Error code + */ +OPE_EXPORT int ope_encoder_continue_new_callbacks(OggOpusEnc *enc, void *user_data, OggOpusComments *comments); + +/** Write out the header now rather than wait for audio to begin. + \param[in,out] enc Encoder + \return Error code + */ +OPE_EXPORT int ope_encoder_flush_header(OggOpusEnc *enc); + +/** Sets encoder options. + \param[in,out] enc Encoder + \param request Use a request macro + \return Error code + */ +OPE_EXPORT int ope_encoder_ctl(OggOpusEnc *enc, int request, ...); + +/** Converts a libopusenc error code into a human readable string. + * + * @param error Error number + * @returns Error string + */ +OPE_EXPORT const char *ope_strerror(int error); + +/** Returns a string representing the version of libopusenc being used at run time. + \return A string describing the version of this library */ +OPE_EXPORT const char *ope_get_version_string(void); + +/** ABI version for this header. Can be used to check for features at run time. + \return An integer representing the ABI version */ +OPE_EXPORT int ope_get_abi_version(void); + +/*@}*/ +/*@}*/ + +# if defined(__cplusplus) +} +# endif + +#endif diff --git a/library/opusencoder/src/main/cpp/opus/libs/arm64-v8a/libopus.so b/library/opusencoder/src/main/cpp/opus/libs/arm64-v8a/libopus.so new file mode 100755 index 0000000000..466c1df13a Binary files /dev/null and b/library/opusencoder/src/main/cpp/opus/libs/arm64-v8a/libopus.so differ diff --git a/library/opusencoder/src/main/cpp/opus/libs/arm64-v8a/libopusenc.so b/library/opusencoder/src/main/cpp/opus/libs/arm64-v8a/libopusenc.so new file mode 100755 index 0000000000..9389efd919 Binary files /dev/null and b/library/opusencoder/src/main/cpp/opus/libs/arm64-v8a/libopusenc.so differ diff --git a/library/opusencoder/src/main/cpp/opus/libs/armeabi-v7a/libopus.so b/library/opusencoder/src/main/cpp/opus/libs/armeabi-v7a/libopus.so new file mode 100755 index 0000000000..d4bb6d427e Binary files /dev/null and b/library/opusencoder/src/main/cpp/opus/libs/armeabi-v7a/libopus.so differ diff --git a/library/opusencoder/src/main/cpp/opus/libs/armeabi-v7a/libopusenc.so b/library/opusencoder/src/main/cpp/opus/libs/armeabi-v7a/libopusenc.so new file mode 100755 index 0000000000..655f296d90 Binary files /dev/null and b/library/opusencoder/src/main/cpp/opus/libs/armeabi-v7a/libopusenc.so differ diff --git a/library/opusencoder/src/main/cpp/opus/libs/x86/libopus.so b/library/opusencoder/src/main/cpp/opus/libs/x86/libopus.so new file mode 100755 index 0000000000..0acf14474b Binary files /dev/null and b/library/opusencoder/src/main/cpp/opus/libs/x86/libopus.so differ diff --git a/library/opusencoder/src/main/cpp/opus/libs/x86/libopusenc.so b/library/opusencoder/src/main/cpp/opus/libs/x86/libopusenc.so new file mode 100755 index 0000000000..f862ea15f7 Binary files /dev/null and b/library/opusencoder/src/main/cpp/opus/libs/x86/libopusenc.so differ diff --git a/library/opusencoder/src/main/cpp/opus/libs/x86_64/libopus.so b/library/opusencoder/src/main/cpp/opus/libs/x86_64/libopus.so new file mode 100755 index 0000000000..1480c8c586 Binary files /dev/null and b/library/opusencoder/src/main/cpp/opus/libs/x86_64/libopus.so differ diff --git a/library/opusencoder/src/main/cpp/opus/libs/x86_64/libopusenc.so b/library/opusencoder/src/main/cpp/opus/libs/x86_64/libopusenc.so new file mode 100755 index 0000000000..f744374fe0 Binary files /dev/null and b/library/opusencoder/src/main/cpp/opus/libs/x86_64/libopusenc.so differ diff --git a/library/opusencoder/src/main/cpp/opuscodec.cpp b/library/opusencoder/src/main/cpp/opuscodec.cpp new file mode 100644 index 0000000000..51bd656c5d --- /dev/null +++ b/library/opusencoder/src/main/cpp/opuscodec.cpp @@ -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. + */ + +#include +#include "codec/CodecOggOpus.h" + +CodecOggOpus oggCodec; + +extern "C" +JNIEXPORT jint JNICALL Java_im_vector_opusencoder_OggOpusEncoder_init(JNIEnv *env, jobject thiz, jstring file_path, jint sample_rate) { + char *path = (char*) env->GetStringUTFChars(file_path, 0); + return oggCodec.encoderInit(path, sample_rate); +} + +extern "C" +JNIEXPORT jint JNICALL Java_im_vector_opusencoder_OggOpusEncoder_writeFrame(JNIEnv *env, jobject thiz, jshortArray shorts, jint samples_per_channel) { + jshort *nativeShorts = env->GetShortArrayElements(shorts, 0); + return oggCodec.writeFrame((short *) nativeShorts, samples_per_channel); +} + +extern "C" +JNIEXPORT jint JNICALL Java_im_vector_opusencoder_OggOpusEncoder_setBitrate(JNIEnv *env, jobject thiz, jint bitrate) { + return oggCodec.setBitrate(bitrate); +} + +extern "C" +JNIEXPORT void JNICALL Java_im_vector_opusencoder_OggOpusEncoder_encoderRelease(JNIEnv *env, jobject thiz) { + oggCodec.encoderRelease(); +} diff --git a/library/opusencoder/src/main/cpp/utils/Logger.h b/library/opusencoder/src/main/cpp/utils/Logger.h new file mode 100644 index 0000000000..9efdc51d41 --- /dev/null +++ b/library/opusencoder/src/main/cpp/utils/Logger.h @@ -0,0 +1,11 @@ +#ifndef ANDROIDOPUSENCODER_LOGGER_H +#define ANDROIDOPUSENCODER_LOGGER_H +#include + + +#define LOGE(tag, ...) __android_log_print(ANDROID_LOG_ERROR, tag, __VA_ARGS__) +#define LOGW(tag, ...) __android_log_print(ANDROID_LOG_WARN, tag, __VA_ARGS__) +#define LOGI(tag, ...) __android_log_print(ANDROID_LOG_INFO, tag, __VA_ARGS__) +#define LOGD(tag, ...) __android_log_print(ANDROID_LOG_DEBUG, tag, __VA_ARGS__) + +#endif //ANDROIDOPUSENCODER_LOGGER_H diff --git a/library/opusencoder/src/main/java/im/vector/opusencoder/OggOpusEncoder.kt b/library/opusencoder/src/main/java/im/vector/opusencoder/OggOpusEncoder.kt new file mode 100644 index 0000000000..8af11f8516 --- /dev/null +++ b/library/opusencoder/src/main/java/im/vector/opusencoder/OggOpusEncoder.kt @@ -0,0 +1,57 @@ +/* + * 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.opusencoder + +import android.util.Log +import androidx.annotation.IntRange +import im.vector.opusencoder.configuration.SampleRate + +/** + * JNI bridge to CodecOggOpus in the native opuscodec library. + */ +class OggOpusEncoder { + + companion object { + + private const val TAG = "OggOpusEncoder" + + init { + try { + System.loadLibrary("opuscodec") + } catch (e: Exception) { + Log.e(TAG, "Couldn't load opus library: $e") + } + } + } + + fun init(filePath: String, sampleRate: SampleRate): Int { + return init(filePath, sampleRate.value) + } + private external fun init(filePath: String, sampleRate: Int): Int + + external fun setBitrate(@IntRange(from = 500, to = 512000) bitrate: Int): Int + + fun encode(shorts: ShortArray, samplesPerChannel: Int): Int { + return writeFrame(shorts, samplesPerChannel) + } + private external fun writeFrame(shorts: ShortArray, samplesPerChannel: Int): Int + + fun release() { + encoderRelease() + } + private external fun encoderRelease() +} diff --git a/library/opusencoder/src/main/java/im/vector/opusencoder/configuration/SampleRate.kt b/library/opusencoder/src/main/java/im/vector/opusencoder/configuration/SampleRate.kt new file mode 100644 index 0000000000..e1a8f10725 --- /dev/null +++ b/library/opusencoder/src/main/java/im/vector/opusencoder/configuration/SampleRate.kt @@ -0,0 +1,28 @@ +/* + * 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.opusencoder.configuration + +/** + * Sampling rate of the input signal in Hz. + */ +sealed class SampleRate private constructor(val value: Int) { + object Rate8khz : SampleRate(8000) + object Rate12kHz : SampleRate(12000) + object Rate16kHz : SampleRate(16000) + object Rate24KHz : SampleRate(24000) + object Rate48kHz : SampleRate(48000) +} diff --git a/library/ui-styles/build.gradle b/library/ui-styles/build.gradle index 0ac513b252..eabd0f36f6 100644 --- a/library/ui-styles/build.gradle +++ b/library/ui-styles/build.gradle @@ -56,8 +56,6 @@ dependencies { implementation libs.google.material // Pref theme implementation libs.androidx.preferenceKtx - // PFLockScreen attrs - implementation 'com.github.vector-im:PFLockScreen-Android:1.0.0-beta12' // dialpad dimen implementation 'im.dlg:android-dialer:1.2.5' -} \ No newline at end of file +} diff --git a/library/ui-styles/src/main/res/drawable/bg_pin_key.xml b/library/ui-styles/src/main/res/drawable/bg_pin_key.xml index d4a54577be..5bf293aab0 100644 --- a/library/ui-styles/src/main/res/drawable/bg_pin_key.xml +++ b/library/ui-styles/src/main/res/drawable/bg_pin_key.xml @@ -10,4 +10,4 @@ android:height="70dp" /> - \ No newline at end of file + diff --git a/library/ui-styles/src/main/res/drawable/lockscreen_background.xml b/library/ui-styles/src/main/res/drawable/lockscreen_background.xml new file mode 100644 index 0000000000..5688c433f7 --- /dev/null +++ b/library/ui-styles/src/main/res/drawable/lockscreen_background.xml @@ -0,0 +1,9 @@ + + + + + \ No newline at end of file diff --git a/library/ui-styles/src/main/res/drawable/lockscreen_circle_background.xml b/library/ui-styles/src/main/res/drawable/lockscreen_circle_background.xml new file mode 100644 index 0000000000..87fa99063c --- /dev/null +++ b/library/ui-styles/src/main/res/drawable/lockscreen_circle_background.xml @@ -0,0 +1,12 @@ + + + + + + + \ No newline at end of file diff --git a/library/ui-styles/src/main/res/drawable/lockscreen_circle_code_empty.xml b/library/ui-styles/src/main/res/drawable/lockscreen_circle_code_empty.xml new file mode 100644 index 0000000000..abde6087e0 --- /dev/null +++ b/library/ui-styles/src/main/res/drawable/lockscreen_circle_code_empty.xml @@ -0,0 +1,13 @@ + + + + + + + diff --git a/library/ui-styles/src/main/res/drawable/lockscreen_circle_code_fill.xml b/library/ui-styles/src/main/res/drawable/lockscreen_circle_code_fill.xml new file mode 100644 index 0000000000..e3f1082324 --- /dev/null +++ b/library/ui-styles/src/main/res/drawable/lockscreen_circle_code_fill.xml @@ -0,0 +1,16 @@ + + + + + + + + + diff --git a/library/ui-styles/src/main/res/drawable/lockscreen_circle_key_selector.xml b/library/ui-styles/src/main/res/drawable/lockscreen_circle_key_selector.xml new file mode 100644 index 0000000000..3fdebfbbe0 --- /dev/null +++ b/library/ui-styles/src/main/res/drawable/lockscreen_circle_key_selector.xml @@ -0,0 +1,7 @@ + + + + + + \ No newline at end of file diff --git a/library/ui-styles/src/main/res/drawable/lockscreen_code_selector.xml b/library/ui-styles/src/main/res/drawable/lockscreen_code_selector.xml new file mode 100644 index 0000000000..5de4957a3b --- /dev/null +++ b/library/ui-styles/src/main/res/drawable/lockscreen_code_selector.xml @@ -0,0 +1,9 @@ + + + + + + diff --git a/library/ui-styles/src/main/res/drawable/lockscreen_delete.xml b/library/ui-styles/src/main/res/drawable/lockscreen_delete.xml new file mode 100644 index 0000000000..e1d70e8f41 --- /dev/null +++ b/library/ui-styles/src/main/res/drawable/lockscreen_delete.xml @@ -0,0 +1,7 @@ + + + + diff --git a/library/ui-styles/src/main/res/drawable/lockscreen_fingerprint.xml b/library/ui-styles/src/main/res/drawable/lockscreen_fingerprint.xml new file mode 100644 index 0000000000..7f0abe850a --- /dev/null +++ b/library/ui-styles/src/main/res/drawable/lockscreen_fingerprint.xml @@ -0,0 +1,5 @@ + + + diff --git a/library/ui-styles/src/main/res/drawable/lockscreen_side_button_background.xml b/library/ui-styles/src/main/res/drawable/lockscreen_side_button_background.xml new file mode 100644 index 0000000000..b205b2d91c --- /dev/null +++ b/library/ui-styles/src/main/res/drawable/lockscreen_side_button_background.xml @@ -0,0 +1,29 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/library/ui-styles/src/main/res/drawable/lockscreen_touch_selector.xml b/library/ui-styles/src/main/res/drawable/lockscreen_touch_selector.xml new file mode 100644 index 0000000000..141f2ac698 --- /dev/null +++ b/library/ui-styles/src/main/res/drawable/lockscreen_touch_selector.xml @@ -0,0 +1,17 @@ + + + + + + + + + + diff --git a/library/ui-styles/src/main/res/drawable/pin_code_dot_empty.xml b/library/ui-styles/src/main/res/drawable/pin_code_dot_empty.xml index 1827a7682b..879cac15ca 100644 --- a/library/ui-styles/src/main/res/drawable/pin_code_dot_empty.xml +++ b/library/ui-styles/src/main/res/drawable/pin_code_dot_empty.xml @@ -10,4 +10,4 @@ - \ No newline at end of file + diff --git a/library/ui-styles/src/main/res/drawable/pin_code_dot_fill.xml b/library/ui-styles/src/main/res/drawable/pin_code_dot_fill.xml index 799ea30174..83bdac5126 100644 --- a/library/ui-styles/src/main/res/drawable/pin_code_dot_fill.xml +++ b/library/ui-styles/src/main/res/drawable/pin_code_dot_fill.xml @@ -9,4 +9,4 @@ - \ No newline at end of file + diff --git a/library/ui-styles/src/main/res/drawable/pin_code_dots.xml b/library/ui-styles/src/main/res/drawable/pin_code_dots.xml index 29e445e511..c4b1073f85 100644 --- a/library/ui-styles/src/main/res/drawable/pin_code_dots.xml +++ b/library/ui-styles/src/main/res/drawable/pin_code_dots.xml @@ -6,4 +6,4 @@ android:drawable="@drawable/pin_code_dot_fill"/> - \ No newline at end of file + diff --git a/library/ui-styles/src/main/res/values-land/lockscreen_default_dimen.xml b/library/ui-styles/src/main/res/values-land/lockscreen_default_dimen.xml new file mode 100644 index 0000000000..2ae3ca0689 --- /dev/null +++ b/library/ui-styles/src/main/res/values-land/lockscreen_default_dimen.xml @@ -0,0 +1,5 @@ + + + 60dp + 15dp + diff --git a/library/ui-styles/src/main/res/values/dimens_font.xml b/library/ui-styles/src/main/res/values/dimens_font.xml index 1b5a826acb..ad8f012a16 100644 --- a/library/ui-styles/src/main/res/values/dimens_font.xml +++ b/library/ui-styles/src/main/res/values/dimens_font.xml @@ -7,7 +7,8 @@ 14sp 12sp 10sp + 8sp 14sp - \ No newline at end of file + diff --git a/library/ui-styles/src/main/res/values/lockscreen_attr.xml b/library/ui-styles/src/main/res/values/lockscreen_attr.xml new file mode 100644 index 0000000000..64e77d3c4e --- /dev/null +++ b/library/ui-styles/src/main/res/values/lockscreen_attr.xml @@ -0,0 +1,12 @@ + + + + + + + + + + + + diff --git a/library/ui-styles/src/main/res/values/lockscreen_default_colors.xml b/library/ui-styles/src/main/res/values/lockscreen_default_colors.xml new file mode 100644 index 0000000000..eb9115d636 --- /dev/null +++ b/library/ui-styles/src/main/res/values/lockscreen_default_colors.xml @@ -0,0 +1,8 @@ + + + #ffffff + #66ffffff + #42000000 + #f4511e + #009688 + diff --git a/library/ui-styles/src/main/res/values/lockscreen_default_dimens.xml b/library/ui-styles/src/main/res/values/lockscreen_default_dimens.xml new file mode 100644 index 0000000000..7d30f179a6 --- /dev/null +++ b/library/ui-styles/src/main/res/values/lockscreen_default_dimens.xml @@ -0,0 +1,7 @@ + + + 70dp + 25dp + 10dp + 5dp + diff --git a/library/ui-styles/src/main/res/values/lockscreen_default_strings.xml b/library/ui-styles/src/main/res/values/lockscreen_default_strings.xml new file mode 100644 index 0000000000..f0d7a75851 --- /dev/null +++ b/library/ui-styles/src/main/res/values/lockscreen_default_strings.xml @@ -0,0 +1,17 @@ + + Cancel + Use pin + Sign in + Next + Forgot? + Input pin code or use biometric authentication + Fingerprint not recognized. Try again + Fingerprint recognized + + Confirm fingerprint to continue + Touch sensor + Fingerprint icon + Confirm PIN + Logo + + diff --git a/library/ui-styles/src/main/res/values/lockscreen_default_styles.xml b/library/ui-styles/src/main/res/values/lockscreen_default_styles.xml new file mode 100644 index 0000000000..dba92df0bb --- /dev/null +++ b/library/ui-styles/src/main/res/values/lockscreen_default_styles.xml @@ -0,0 +1,49 @@ + + + + + + + + + + + + + + + + + + + + + + diff --git a/library/ui-styles/src/main/res/values/styles_bottom_sheet.xml b/library/ui-styles/src/main/res/values/styles_bottom_sheet.xml index 9f17342ede..f6c30040d9 100644 --- a/library/ui-styles/src/main/res/values/styles_bottom_sheet.xml +++ b/library/ui-styles/src/main/res/values/styles_bottom_sheet.xml @@ -4,10 +4,13 @@ - \ No newline at end of file + diff --git a/library/ui-styles/src/main/res/values/styles_buttons.xml b/library/ui-styles/src/main/res/values/styles_buttons.xml index 004aca5aaa..c8dcacb8ed 100644 --- a/library/ui-styles/src/main/res/values/styles_buttons.xml +++ b/library/ui-styles/src/main/res/values/styles_buttons.xml @@ -65,4 +65,4 @@ ?colorOnPrimary - \ No newline at end of file + diff --git a/library/ui-styles/src/main/res/values/styles_location.xml b/library/ui-styles/src/main/res/values/styles_location.xml index 7571265241..6777450a63 100644 --- a/library/ui-styles/src/main/res/values/styles_location.xml +++ b/library/ui-styles/src/main/res/values/styles_location.xml @@ -18,4 +18,31 @@ center + + + + + + + + + + diff --git a/library/ui-styles/src/main/res/values/styles_pin_code.xml b/library/ui-styles/src/main/res/values/styles_pin_code.xml index 2b6c113359..8459778e29 100644 --- a/library/ui-styles/src/main/res/values/styles_pin_code.xml +++ b/library/ui-styles/src/main/res/values/styles_pin_code.xml @@ -22,13 +22,13 @@ @@ -41,4 +41,4 @@ ?vctr_content_primary - \ No newline at end of file + diff --git a/library/ui-styles/src/main/res/values/styles_text_view.xml b/library/ui-styles/src/main/res/values/styles_text_view.xml index 77e32da345..0dcaf30f48 100644 --- a/library/ui-styles/src/main/res/values/styles_text_view.xml +++ b/library/ui-styles/src/main/res/values/styles_text_view.xml @@ -48,4 +48,9 @@ 16sp - \ No newline at end of file + + + diff --git a/library/ui-styles/src/main/res/values/styles_timeline.xml b/library/ui-styles/src/main/res/values/styles_timeline.xml index c86eeb8efb..20c375c2d6 100644 --- a/library/ui-styles/src/main/res/values/styles_timeline.xml +++ b/library/ui-styles/src/main/res/values/styles_timeline.xml @@ -1,5 +1,5 @@ - + + - \ No newline at end of file + diff --git a/library/ui-styles/src/main/res/values/text_appearances.xml b/library/ui-styles/src/main/res/values/text_appearances.xml index 8e30dd00d6..1e60e05acf 100644 --- a/library/ui-styles/src/main/res/values/text_appearances.xml +++ b/library/ui-styles/src/main/res/values/text_appearances.xml @@ -85,4 +85,11 @@ 0.02 + + diff --git a/library/ui-styles/src/main/res/values/theme_dark.xml b/library/ui-styles/src/main/res/values/theme_dark.xml index 733f7e8eb5..f86a05ed66 100644 --- a/library/ui-styles/src/main/res/values/theme_dark.xml +++ b/library/ui-styles/src/main/res/values/theme_dark.xml @@ -111,14 +111,14 @@ @style/PreferenceThemeOverlay.v14.Material - @style/PinCodeScreenStyle - @style/PinCodeKeyButtonStyle - @style/PinCodeTitleStyle - @style/PinCodeHintStyle - @style/PinCodeDotsViewStyle - @style/PinCodeDeleteButtonStyle - @style/PinCodeFingerprintButtonStyle - @style/PinCodeNextButtonStyle + @style/PinCodeScreenStyle + @style/PinCodeKeyButtonStyle + @style/PinCodeTitleStyle + @style/PinCodeHintStyle + @style/PinCodeDotsViewStyle + @style/PinCodeDeleteButtonStyle + @style/PinCodeFingerprintButtonStyle + @style/PinCodeNextButtonStyle @color/android_status_bar_background_dark @color/android_navigation_bar_background_dark diff --git a/library/ui-styles/src/main/res/values/theme_light.xml b/library/ui-styles/src/main/res/values/theme_light.xml index 77996c8ce5..173b502dcd 100644 --- a/library/ui-styles/src/main/res/values/theme_light.xml +++ b/library/ui-styles/src/main/res/values/theme_light.xml @@ -111,14 +111,14 @@ @style/PreferenceThemeOverlay.v14.Material - @style/PinCodeScreenStyle - @style/PinCodeKeyButtonStyle - @style/PinCodeTitleStyle - @style/PinCodeHintStyle - @style/PinCodeDotsViewStyle - @style/PinCodeDeleteButtonStyle - @style/PinCodeFingerprintButtonStyle - @style/PinCodeNextButtonStyle + @style/PinCodeScreenStyle + @style/PinCodeKeyButtonStyle + @style/PinCodeTitleStyle + @style/PinCodeHintStyle + @style/PinCodeDotsViewStyle + @style/PinCodeDeleteButtonStyle + @style/PinCodeFingerprintButtonStyle + @style/PinCodeNextButtonStyle @color/android_status_bar_background_dark diff --git a/matrix-sdk-android-flow/build.gradle b/matrix-sdk-android-flow/build.gradle index ea43ce20c8..fb69af2d82 100644 --- a/matrix-sdk-android-flow/build.gradle +++ b/matrix-sdk-android-flow/build.gradle @@ -31,9 +31,7 @@ android { } dependencies { - implementation project(":matrix-sdk-android") - implementation libs.androidx.appCompat implementation libs.jetbrains.coroutinesCore implementation libs.jetbrains.coroutinesAndroid @@ -41,7 +39,4 @@ dependencies { // Paging implementation libs.androidx.pagingRuntimeKtx - - // Logging - implementation libs.jakewharton.timber } 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 90546756b8..7ac81b2d86 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 @@ -18,7 +18,7 @@ package org.matrix.android.sdk.flow import androidx.lifecycle.asFlow import kotlinx.coroutines.flow.Flow -import org.matrix.android.sdk.api.query.QueryStringValue +import org.matrix.android.sdk.api.query.QueryStateEventValue 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 @@ -67,17 +67,17 @@ class FlowRoom(private val room: Room) { } } - fun liveStateEvent(eventType: String, stateKey: QueryStringValue): Flow> { + fun liveStateEvent(eventType: String, stateKey: QueryStateEventValue): Flow> { return room.stateService().getStateEventLive(eventType, stateKey).asFlow() .startWith(room.coroutineDispatchers.io) { room.getStateEvent(eventType, stateKey).toOptional() } } - fun liveStateEvents(eventTypes: Set): Flow> { - return room.stateService().getStateEventsLive(eventTypes).asFlow() + fun liveStateEvents(eventTypes: Set, stateKey: QueryStateEventValue): Flow> { + return room.stateService().getStateEventsLive(eventTypes, stateKey).asFlow() .startWith(room.coroutineDispatchers.io) { - room.stateService().getStateEvents(eventTypes) + room.stateService().getStateEvents(eventTypes, stateKey) } } 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 9f260858f6..cc73e099b6 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 @@ -19,7 +19,7 @@ package org.matrix.android.sdk.flow import androidx.lifecycle.asFlow import androidx.paging.PagedList import kotlinx.coroutines.flow.Flow -import org.matrix.android.sdk.api.query.QueryStringValue +import org.matrix.android.sdk.api.query.QueryStateEventValue import org.matrix.android.sdk.api.session.Session import org.matrix.android.sdk.api.session.accountdata.UserAccountDataEvent import org.matrix.android.sdk.api.session.crypto.crosssigning.MXCrossSigningInfo @@ -45,6 +45,13 @@ import org.matrix.android.sdk.api.util.toOptional class FlowSession(private val session: Session) { + fun liveRoomSummary(roomId: String): Flow> { + return session.roomService().getRoomSummaryLive(roomId).asFlow() + .startWith(session.coroutineDispatchers.io) { + session.roomService().getRoomSummary(roomId).toOptional() + } + } + fun liveRoomSummaries(queryParams: RoomSummaryQueryParams, sortOrder: RoomSortOrder = RoomSortOrder.NONE): Flow> { return session.roomService().getRoomSummariesLive(queryParams, sortOrder).asFlow() .startWith(session.coroutineDispatchers.io) { @@ -81,7 +88,7 @@ class FlowSession(private val session: Session) { } fun liveSyncState(): Flow { - return session.getSyncStateLive().asFlow() + return session.syncService().getSyncStateLive().asFlow() } fun livePushers(): Flow> { @@ -172,7 +179,7 @@ class FlowSession(private val session: Session) { fun liveRoomWidgets( roomId: String, - widgetId: QueryStringValue, + widgetId: QueryStateEventValue, widgetTypes: Set? = null, excludedTypes: Set? = null ): Flow> { diff --git a/matrix-sdk-android/build.gradle b/matrix-sdk-android/build.gradle index 33798d1357..b3a7a9a506 100644 --- a/matrix-sdk-android/build.gradle +++ b/matrix-sdk-android/build.gradle @@ -5,6 +5,10 @@ apply plugin: 'kotlin-parcelize' apply plugin: 'realm-android' apply plugin: "org.jetbrains.dokka" +if (project.hasProperty("coverage")) { + apply plugin: 'jacoco' +} + buildscript { repositories { // Do not use `mavenCentral()`, it prevents Dependabot from working properly @@ -56,7 +60,7 @@ android { // that the app's state is completely cleared between tests. testInstrumentationRunnerArguments clearPackageData: 'true' - buildConfigField "String", "SDK_VERSION", "\"1.4.18\"" + buildConfigField "String", "SDK_VERSION", "\"1.4.26\"" buildConfigField "String", "GIT_SDK_REVISION", "\"${gitRevision()}\"" buildConfigField "String", "GIT_SDK_REVISION_UNIX_DATE", "\"${gitRevisionUnixDate()}\"" @@ -74,6 +78,9 @@ android { buildTypes { debug { + if (project.hasProperty("coverage")) { + testCoverageEnabled = coverage.enableTestCoverage + } // Set to true to log privacy or sensible data, such as token buildConfigField "boolean", "LOG_PRIVATE_DATA", project.property("vector.debugPrivateData") // Set to BODY instead of NONE to enable logging @@ -136,7 +143,6 @@ dependencies { implementation libs.jetbrains.coroutinesCore implementation libs.jetbrains.coroutinesAndroid - implementation libs.androidx.appCompat implementation libs.androidx.core // Lifecycle @@ -152,15 +158,14 @@ dependencies { // - https://github.com/square/okhttp/issues/3278 // - https://github.com/square/okhttp/issues/4455 // - https://github.com/square/okhttp/issues/3146 - implementation(platform("com.squareup.okhttp3:okhttp-bom:4.9.3")) + implementation(platform("com.squareup.okhttp3:okhttp-bom:4.10.0")) implementation 'com.squareup.okhttp3:okhttp' implementation 'com.squareup.okhttp3:logging-interceptor' - implementation 'com.squareup.okhttp3:okhttp-urlconnection' implementation libs.squareup.moshi kapt libs.squareup.moshiKotlin - implementation libs.markwon.core + api "com.atlassian.commonmark:commonmark:0.13.0" // Image implementation libs.androidx.exifinterface @@ -176,12 +181,8 @@ dependencies { // Work implementation libs.androidx.work - // FP - implementation libs.arrow.core - implementation libs.arrow.instances - // olm lib is now hosted in MavenCentral - implementation 'org.matrix.android:olm-sdk:3.2.11' + implementation 'org.matrix.android:olm-sdk:3.2.12' // DI implementation libs.dagger.dagger @@ -198,11 +199,9 @@ dependencies { implementation libs.apache.commonsImaging // Phone number https://github.com/google/libphonenumber - implementation 'com.googlecode.libphonenumber:libphonenumber:8.12.48' + implementation 'com.googlecode.libphonenumber:libphonenumber:8.12.50' testImplementation libs.tests.junit - testImplementation 'org.robolectric:robolectric:4.7.3' - //testImplementation 'org.robolectric:shadows-support-v4:3.0' // Note: version sticks to 1.9.2 due to https://github.com/mockk/mockk/issues/281 testImplementation libs.mockk.mockk testImplementation libs.tests.kluent diff --git a/matrix-sdk-android/docs/packages.md b/matrix-sdk-android/docs/packages.md index ae7bee1b4e..19f7c15a7a 100644 --- a/matrix-sdk-android/docs/packages.md +++ b/matrix-sdk-android/docs/packages.md @@ -1,3 +1,7 @@ +# Package org.matrix.android.sdk.userstories + +This package contains some user stories (**Us** prefix) of the SDK usage. You will find example of what it is possible to do with the SDK and the API which can be used to do it. + # 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/InstrumentedTest.kt b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/InstrumentedTest.kt index a763766821..f08f0a28ed 100644 --- a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/InstrumentedTest.kt +++ b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/InstrumentedTest.kt @@ -19,10 +19,14 @@ package org.matrix.android.sdk import android.content.Context import androidx.test.core.app.ApplicationProvider import org.junit.Rule +import org.matrix.android.sdk.common.RetryTestRule import org.matrix.android.sdk.test.shared.createTimberTestRule interface InstrumentedTest { + @Rule + fun retryTestRule() = RetryTestRule(3) + @Rule fun timberTestRule() = createTimberTestRule() diff --git a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/session/securestorage/TestBuildVersionSdkIntProvider.kt b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/TestBuildVersionSdkIntProvider.kt similarity index 78% rename from matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/session/securestorage/TestBuildVersionSdkIntProvider.kt rename to matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/TestBuildVersionSdkIntProvider.kt index b08c88fb24..d0d64491ef 100644 --- a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/session/securestorage/TestBuildVersionSdkIntProvider.kt +++ b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/TestBuildVersionSdkIntProvider.kt @@ -1,5 +1,5 @@ /* - * Copyright (c) 2021 The Matrix.org Foundation C.I.C. + * Copyright 2022 The Matrix.org Foundation C.I.C. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,9 +14,9 @@ * limitations under the License. */ -package org.matrix.android.sdk.internal.session.securestorage +package org.matrix.android.sdk -import org.matrix.android.sdk.internal.util.system.BuildVersionSdkIntProvider +import org.matrix.android.sdk.api.util.BuildVersionSdkIntProvider class TestBuildVersionSdkIntProvider : BuildVersionSdkIntProvider { var value: Int = 0 diff --git a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/account/AccountCreationTest.kt b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/account/AccountCreationTest.kt index 486bc02769..7e4fc4768f 100644 --- a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/account/AccountCreationTest.kt +++ b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/account/AccountCreationTest.kt @@ -24,8 +24,8 @@ import org.junit.runner.RunWith import org.junit.runners.JUnit4 import org.junit.runners.MethodSorters import org.matrix.android.sdk.InstrumentedTest -import org.matrix.android.sdk.common.CommonTestHelper -import org.matrix.android.sdk.common.CryptoTestHelper +import org.matrix.android.sdk.common.CommonTestHelper.Companion.runCryptoTest +import org.matrix.android.sdk.common.CommonTestHelper.Companion.runSessionTest import org.matrix.android.sdk.common.SessionTestParams import org.matrix.android.sdk.common.TestConstants @@ -34,32 +34,22 @@ import org.matrix.android.sdk.common.TestConstants @LargeTest class AccountCreationTest : InstrumentedTest { - private val commonTestHelper = CommonTestHelper(context()) - private val cryptoTestHelper = CryptoTestHelper(commonTestHelper) - @Test - fun createAccountTest() { - val session = commonTestHelper.createAccount(TestConstants.USER_ALICE, SessionTestParams(withInitialSync = true)) - - commonTestHelper.signOutAndClose(session) + fun createAccountTest() = runSessionTest(context()) { commonTestHelper -> + commonTestHelper.createAccount(TestConstants.USER_ALICE, SessionTestParams(withInitialSync = true)) } @Test @Ignore("This test will be ignored until it is fixed") - fun createAccountAndLoginAgainTest() { + fun createAccountAndLoginAgainTest() = runSessionTest(context()) { commonTestHelper -> val session = commonTestHelper.createAccount(TestConstants.USER_ALICE, SessionTestParams(withInitialSync = true)) // Log again to the same account - val session2 = commonTestHelper.logIntoAccount(session.myUserId, SessionTestParams(withInitialSync = true)) - - commonTestHelper.signOutAndClose(session) - commonTestHelper.signOutAndClose(session2) + commonTestHelper.logIntoAccount(session.myUserId, SessionTestParams(withInitialSync = true)) } @Test - fun simpleE2eTest() { - val res = cryptoTestHelper.doE2ETestWithAliceInARoom() - - res.cleanUp(commonTestHelper) + fun simpleE2eTest() = runCryptoTest(context()) { cryptoTestHelper, _ -> + cryptoTestHelper.doE2ETestWithAliceInARoom() } } 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 6d740c5a34..260e8dbe05 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 @@ -25,7 +25,7 @@ import org.junit.runners.JUnit4 import org.junit.runners.MethodSorters import org.matrix.android.sdk.InstrumentedTest import org.matrix.android.sdk.api.failure.isInvalidPassword -import org.matrix.android.sdk.common.CommonTestHelper +import org.matrix.android.sdk.common.CommonTestHelper.Companion.runSessionTest import org.matrix.android.sdk.common.SessionTestParams import org.matrix.android.sdk.common.TestConstants @@ -34,14 +34,12 @@ import org.matrix.android.sdk.common.TestConstants @Ignore("This test will be ignored until it is fixed") class ChangePasswordTest : InstrumentedTest { - private val commonTestHelper = CommonTestHelper(context()) - companion object { private const val NEW_PASSWORD = "this is a new password" } @Test - fun changePasswordTest() { + fun changePasswordTest() = runSessionTest(context()) { commonTestHelper -> val session = commonTestHelper.createAccount(TestConstants.USER_ALICE, SessionTestParams(withInitialSync = false)) // Change password @@ -54,9 +52,6 @@ class ChangePasswordTest : InstrumentedTest { throwable.isInvalidPassword().shouldBeTrue() // Try to login with the new password, should work - val session2 = commonTestHelper.logIntoAccount(session.myUserId, NEW_PASSWORD, SessionTestParams(withInitialSync = false)) - - commonTestHelper.signOutAndClose(session) - commonTestHelper.signOutAndClose(session2) + commonTestHelper.logIntoAccount(session.myUserId, NEW_PASSWORD, SessionTestParams(withInitialSync = false)) } } 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 d2dfe4d945..0b21f85742 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 @@ -29,7 +29,7 @@ import org.matrix.android.sdk.api.auth.UserPasswordAuth import org.matrix.android.sdk.api.auth.registration.RegistrationFlowResponse import org.matrix.android.sdk.api.failure.Failure import org.matrix.android.sdk.api.failure.MatrixError -import org.matrix.android.sdk.common.CommonTestHelper +import org.matrix.android.sdk.common.CommonTestHelper.Companion.runSessionTest import org.matrix.android.sdk.common.SessionTestParams import org.matrix.android.sdk.common.TestConstants import kotlin.coroutines.Continuation @@ -39,10 +39,8 @@ import kotlin.coroutines.resume @FixMethodOrder(MethodSorters.JVM) class DeactivateAccountTest : InstrumentedTest { - private val commonTestHelper = CommonTestHelper(context()) - @Test - fun deactivateAccountTest() { + fun deactivateAccountTest() = runSessionTest(context(), false /* session will be deactivated */) { commonTestHelper -> val session = commonTestHelper.createAccount(TestConstants.USER_ALICE, SessionTestParams(withInitialSync = true)) // Deactivate the account diff --git a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/api/network/ApiInterceptorTest.kt b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/api/network/ApiInterceptorTest.kt index 9371154aaf..8dbff82015 100644 --- a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/api/network/ApiInterceptorTest.kt +++ b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/api/network/ApiInterceptorTest.kt @@ -23,7 +23,7 @@ import org.junit.runner.RunWith import org.junit.runners.JUnit4 import org.junit.runners.MethodSorters import org.matrix.android.sdk.InstrumentedTest -import org.matrix.android.sdk.common.CommonTestHelper +import org.matrix.android.sdk.common.CommonTestHelper.Companion.runSessionTest import org.matrix.android.sdk.common.SessionTestParams import org.matrix.android.sdk.common.TestConstants import timber.log.Timber @@ -32,10 +32,8 @@ import timber.log.Timber @FixMethodOrder(MethodSorters.JVM) class ApiInterceptorTest : InstrumentedTest { - private val commonTestHelper = CommonTestHelper(context()) - @Test - fun apiInterceptorTest() { + fun apiInterceptorTest() = runSessionTest(context()) { commonTestHelper -> val responses = mutableListOf() val listener = object : ApiInterceptorListener { diff --git a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/session/securestorage/SecretStoringUtilsTest.kt b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/api/securestorage/SecretStoringUtilsTest.kt similarity index 62% rename from matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/session/securestorage/SecretStoringUtilsTest.kt rename to matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/api/securestorage/SecretStoringUtilsTest.kt index 6bcd12742b..14f985243c 100644 --- a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/session/securestorage/SecretStoringUtilsTest.kt +++ b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/api/securestorage/SecretStoringUtilsTest.kt @@ -14,40 +14,57 @@ * limitations under the License. */ -package org.matrix.android.sdk.internal.session.securestorage +package org.matrix.android.sdk.api.securestorage import android.os.Build +import android.util.Base64 import androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.test.platform.app.InstrumentationRegistry +import io.mockk.clearAllMocks +import io.mockk.every +import io.mockk.spyk +import org.amshove.kluent.invoking import org.amshove.kluent.shouldBeEqualTo +import org.amshove.kluent.shouldBeInstanceOf +import org.amshove.kluent.shouldNotThrow +import org.amshove.kluent.shouldThrow +import org.junit.Before import org.junit.FixMethodOrder import org.junit.Test import org.junit.runner.RunWith import org.junit.runners.MethodSorters -import org.matrix.android.sdk.InstrumentedTest -import org.matrix.android.sdk.api.util.fromBase64 -import org.matrix.android.sdk.api.util.toBase64NoPadding +import org.matrix.android.sdk.TestBuildVersionSdkIntProvider import java.io.ByteArrayOutputStream +import java.security.KeyStore +import java.security.KeyStoreException import java.util.UUID @RunWith(AndroidJUnit4::class) @FixMethodOrder(MethodSorters.JVM) -class SecretStoringUtilsTest : InstrumentedTest { +class SecretStoringUtilsTest { + private val context = InstrumentationRegistry.getInstrumentation().targetContext private val buildVersionSdkIntProvider = TestBuildVersionSdkIntProvider() - private val secretStoringUtils = SecretStoringUtils(context(), buildVersionSdkIntProvider) + private val keyStore = spyk(KeyStore.getInstance("AndroidKeyStore")).also { it.load(null) } + private val secretStoringUtils = SecretStoringUtils(context, keyStore, buildVersionSdkIntProvider) companion object { const val TEST_STR = "This is something I want to store safely!" } + @Before + fun setup() { + clearAllMocks() + } + @Test fun testStringNominalCaseApi21() { val alias = generateAlias() buildVersionSdkIntProvider.value = Build.VERSION_CODES.LOLLIPOP // Encrypt - val encrypted = secretStoringUtils.securelyStoreString(TEST_STR, alias) + val encrypted = secretStoringUtils.securelyStoreBytes(TEST_STR.toByteArray(), alias) // Decrypt - val decrypted = secretStoringUtils.loadSecureSecret(encrypted, alias) + val decrypted = String(secretStoringUtils.loadSecureSecretBytes(encrypted, alias)) decrypted shouldBeEqualTo TEST_STR secretStoringUtils.safeDeleteKey(alias) } @@ -57,9 +74,9 @@ class SecretStoringUtilsTest : InstrumentedTest { val alias = generateAlias() buildVersionSdkIntProvider.value = Build.VERSION_CODES.M // Encrypt - val encrypted = secretStoringUtils.securelyStoreString(TEST_STR, alias) + val encrypted = secretStoringUtils.securelyStoreBytes(TEST_STR.toByteArray(), alias) // Decrypt - val decrypted = secretStoringUtils.loadSecureSecret(encrypted, alias) + val decrypted = String(secretStoringUtils.loadSecureSecretBytes(encrypted, alias)) decrypted shouldBeEqualTo TEST_STR secretStoringUtils.safeDeleteKey(alias) } @@ -69,9 +86,9 @@ class SecretStoringUtilsTest : InstrumentedTest { val alias = generateAlias() buildVersionSdkIntProvider.value = Build.VERSION_CODES.R // Encrypt - val encrypted = secretStoringUtils.securelyStoreString(TEST_STR, alias) + val encrypted = secretStoringUtils.securelyStoreBytes(TEST_STR.toByteArray(), alias) // Decrypt - val decrypted = secretStoringUtils.loadSecureSecret(encrypted, alias) + val decrypted = String(secretStoringUtils.loadSecureSecretBytes(encrypted, alias)) decrypted shouldBeEqualTo TEST_STR secretStoringUtils.safeDeleteKey(alias) } @@ -81,13 +98,13 @@ class SecretStoringUtilsTest : InstrumentedTest { val alias = generateAlias() buildVersionSdkIntProvider.value = Build.VERSION_CODES.LOLLIPOP // Encrypt - val encrypted = secretStoringUtils.securelyStoreString(TEST_STR, alias) + val encrypted = secretStoringUtils.securelyStoreBytes(TEST_STR.toByteArray(), alias) // Simulate a system upgrade buildVersionSdkIntProvider.value = Build.VERSION_CODES.M // Decrypt - val decrypted = secretStoringUtils.loadSecureSecret(encrypted, alias) + val decrypted = String(secretStoringUtils.loadSecureSecretBytes(encrypted, alias)) decrypted shouldBeEqualTo TEST_STR secretStoringUtils.safeDeleteKey(alias) } @@ -180,5 +197,56 @@ class SecretStoringUtilsTest : InstrumentedTest { secretStoringUtils.safeDeleteKey(alias) } + @Test + fun testEnsureKeyReturnsSymmetricKeyOnAndroidM() { + buildVersionSdkIntProvider.value = Build.VERSION_CODES.M + val alias = generateAlias() + + val key = secretStoringUtils.ensureKey(alias) + key shouldBeInstanceOf KeyStore.SecretKeyEntry::class + + secretStoringUtils.safeDeleteKey(alias) + } + + @Test + fun testEnsureKeyReturnsPrivateKeyOnAndroidL() { + buildVersionSdkIntProvider.value = Build.VERSION_CODES.LOLLIPOP + val alias = generateAlias() + + val key = secretStoringUtils.ensureKey(alias) + key shouldBeInstanceOf KeyStore.PrivateKeyEntry::class + + secretStoringUtils.safeDeleteKey(alias) + } + + @Test + fun testSafeDeleteCanHandleKeyStoreExceptions() { + every { keyStore.deleteEntry(any()) } throws KeyStoreException() + + invoking { secretStoringUtils.safeDeleteKey(generateAlias()) } shouldNotThrow KeyStoreException::class + } + + @Test + fun testLoadSecureSecretBytesWillThrowOnInvalidStreamFormat() { + invoking { + secretStoringUtils.loadSecureSecretBytes(byteArrayOf(255.toByte()), generateAlias()) + } shouldThrow IllegalArgumentException::class + } + + @Test + fun testLoadSecureSecretWillThrowOnInvalidStreamFormat() { + invoking { + secretStoringUtils.loadSecureSecret(byteArrayOf(255.toByte()).inputStream(), generateAlias()) + } shouldThrow IllegalArgumentException::class + } + private fun generateAlias() = UUID.randomUUID().toString() } + +private fun ByteArray.toBase64NoPadding(): String { + return Base64.encodeToString(this, Base64.NO_PADDING or Base64.NO_WRAP) +} + +private fun String.fromBase64(): ByteArray { + return Base64.decode(this, Base64.DEFAULT) +} 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 96ea99d92f..7dafe33935 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 @@ -54,12 +54,39 @@ import java.util.concurrent.TimeUnit * This class exposes methods to be used in common cases * Registration, login, Sync, Sending messages... */ -class CommonTestHelper(context: Context) { +class CommonTestHelper private constructor(context: Context) { + + companion object { + internal fun runSessionTest(context: Context, autoSignoutOnClose: Boolean = true, block: (CommonTestHelper) -> Unit) { + val testHelper = CommonTestHelper(context) + return try { + block(testHelper) + } finally { + if (autoSignoutOnClose) { + testHelper.cleanUpOpenedSessions() + } + } + } + + internal fun runCryptoTest(context: Context, autoSignoutOnClose: Boolean = true, block: (CryptoTestHelper, CommonTestHelper) -> Unit) { + val testHelper = CommonTestHelper(context) + val cryptoTestHelper = CryptoTestHelper(testHelper) + return try { + block(cryptoTestHelper, testHelper) + } finally { + if (autoSignoutOnClose) { + testHelper.cleanUpOpenedSessions() + } + } + } + } internal val matrix: TestMatrix private val coroutineScope = CoroutineScope(SupervisorJob() + Dispatchers.Main) private var accountNumber = 0 + private val trackedSessions = mutableListOf() + fun getTestInterceptor(session: Session): MockOkHttpInterceptor? = TestModule.interceptorForSession(session.sessionId) as? MockOkHttpInterceptor init { @@ -84,6 +111,15 @@ class CommonTestHelper(context: Context) { return logIntoAccount(userId, TestConstants.PASSWORD, testParams) } + fun cleanUpOpenedSessions() { + trackedSessions.forEach { + runBlockingTest { + it.signOutService().signOut(true) + } + } + trackedSessions.clear() + } + /** * Create a homeserver configuration, with Http connection allowed for test */ @@ -101,11 +137,11 @@ class CommonTestHelper(context: Context) { fun syncSession(session: Session, timeout: Long = TestConstants.timeOutMillis * 10) { val lock = CountDownLatch(1) coroutineScope.launch { - session.startSync(true) - val syncLiveData = session.getSyncStateLive() + session.syncService().startSync(true) + val syncLiveData = session.syncService().getSyncStateLive() val syncObserver = object : Observer { override fun onChanged(t: SyncState?) { - if (session.hasAlreadySynced()) { + if (session.syncService().hasAlreadySynced()) { lock.countDown() syncLiveData.removeObserver(this) } @@ -124,10 +160,10 @@ class CommonTestHelper(context: Context) { fun clearCacheAndSync(session: Session, timeout: Long = TestConstants.timeOutMillis) { waitWithLatch(timeout) { latch -> session.clearCache() - val syncLiveData = session.getSyncStateLive() + val syncLiveData = session.syncService().getSyncStateLive() val syncObserver = object : Observer { override fun onChanged(t: SyncState?) { - if (session.hasAlreadySynced()) { + if (session.syncService().hasAlreadySynced()) { Timber.v("Clear cache and synced") syncLiveData.removeObserver(this) latch.countDown() @@ -135,7 +171,7 @@ class CommonTestHelper(context: Context) { } } syncLiveData.observeForever(syncObserver) - session.startSync(true) + session.syncService().startSync(true) } } @@ -216,7 +252,8 @@ class CommonTestHelper(context: Context) { message: String, numberOfMessages: Int, rootThreadEventId: String, - timeout: Long = TestConstants.timeOutMillis): List { + timeout: Long = TestConstants.timeOutMillis + ): List { val timeline = room.timelineService().createTimeline(null, TimelineSettings(10)) timeline.start() val sentEvents = sendTextMessagesBatched(timeline, room, message, numberOfMessages, timeout, rootThreadEventId) @@ -236,16 +273,20 @@ class CommonTestHelper(context: Context) { * @param testParams test params about the session * @return the session associated with the newly created account */ - private fun createAccount(userNamePrefix: String, - password: String, - testParams: SessionTestParams): Session { + private fun createAccount( + userNamePrefix: String, + password: String, + testParams: SessionTestParams + ): Session { val session = createAccountAndSync( userNamePrefix + "_" + accountNumber++ + "_" + UUID.randomUUID(), password, testParams ) assertNotNull(session) - return session + return session.also { + trackedSessions.add(session) + } } /** @@ -256,12 +297,16 @@ class CommonTestHelper(context: Context) { * @param testParams test params about the session * @return the session associated with the existing account */ - fun logIntoAccount(userId: String, - password: String, - testParams: SessionTestParams): Session { + fun logIntoAccount( + userId: String, + password: String, + testParams: SessionTestParams + ): Session { val session = logAccountAndSync(userId, password, testParams) assertNotNull(session) - return session + return session.also { + trackedSessions.add(session) + } } /** @@ -271,9 +316,11 @@ class CommonTestHelper(context: Context) { * @param password the password * @param sessionTestParams parameters for the test */ - private fun createAccountAndSync(userName: String, - password: String, - sessionTestParams: SessionTestParams): Session { + private fun createAccountAndSync( + userName: String, + password: String, + sessionTestParams: SessionTestParams + ): Session { val hs = createHomeServerConfig() runBlockingTest { @@ -309,9 +356,11 @@ class CommonTestHelper(context: Context) { * @param password the password * @param sessionTestParams session test params */ - private fun logAccountAndSync(userName: String, - password: String, - sessionTestParams: SessionTestParams): Session { + private fun logAccountAndSync( + userName: String, + password: String, + sessionTestParams: SessionTestParams + ): Session { val hs = createHomeServerConfig() runBlockingTest { @@ -337,8 +386,10 @@ class CommonTestHelper(context: Context) { * @param userName the account username * @param password the password */ - fun logAccountWithError(userName: String, - password: String): Throwable { + fun logAccountWithError( + userName: String, + password: String + ): Throwable { val hs = createHomeServerConfig() runBlockingTest { @@ -379,8 +430,8 @@ class CommonTestHelper(context: Context) { */ fun await(latch: CountDownLatch, timeout: Long? = TestConstants.timeOutMillis) { assertTrue( - "Timed out after " + timeout + "ms waiting for something to happen. See stacktrace for cause.", - latch.await(timeout ?: TestConstants.timeOutMillis, TimeUnit.MILLISECONDS) + "Timed out after " + timeout + "ms waiting for something to happen. See stacktrace for cause.", + latch.await(timeout ?: TestConstants.timeOutMillis, TimeUnit.MILLISECONDS) ) } @@ -436,6 +487,7 @@ class CommonTestHelper(context: Context) { fun Iterable.signOutAndClose() = forEach { signOutAndClose(it) } fun signOutAndClose(session: Session) { + trackedSessions.remove(session) runBlockingTest(timeout = 60_000) { session.signOutService().signOut(true) } diff --git a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/common/CryptoTestData.kt b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/common/CryptoTestData.kt index b6bedbd719..41d0d3a7e8 100644 --- a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/common/CryptoTestData.kt +++ b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/common/CryptoTestData.kt @@ -18,8 +18,10 @@ package org.matrix.android.sdk.common import org.matrix.android.sdk.api.session.Session -data class CryptoTestData(val roomId: String, - val sessions: List) { +data class CryptoTestData( + val roomId: String, + val sessions: List +) { val firstSession: Session get() = sessions.first() 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 348841313b..5fd86d4fdb 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 @@ -58,7 +58,7 @@ import org.matrix.android.sdk.api.session.room.model.create.CreateRoomParams import org.matrix.android.sdk.api.session.room.model.message.MessageContent import org.matrix.android.sdk.api.session.room.roomSummaryQueryParams import org.matrix.android.sdk.api.session.securestorage.EmptyKeySigner -import org.matrix.android.sdk.api.session.securestorage.SharedSecretStorageService +import org.matrix.android.sdk.api.session.securestorage.KeyRef import org.matrix.android.sdk.api.util.Optional import org.matrix.android.sdk.api.util.awaitCallback import org.matrix.android.sdk.api.util.toBase64NoPadding @@ -66,7 +66,7 @@ import java.util.UUID import kotlin.coroutines.Continuation import kotlin.coroutines.resume -class CryptoTestHelper(private val testHelper: CommonTestHelper) { +class CryptoTestHelper(val testHelper: CommonTestHelper) { private val messagesFromAlice: List = listOf("0 - Hello I'm Alice!", "4 - Go!") private val messagesFromBob: List = listOf("1 - Hello I'm Bob!", "2 - Isn't life grand?", "3 - Let's go to the opera.") @@ -361,19 +361,19 @@ class CryptoTestHelper(private val testHelper: CommonTestHelper) { ssssService.storeSecret( MASTER_KEY_SSSS_NAME, session.cryptoService().crossSigningService().getCrossSigningPrivateKeys()!!.master!!, - listOf(SharedSecretStorageService.KeyRef(keyInfo.keyId, keyInfo.keySpec)) + listOf(KeyRef(keyInfo.keyId, keyInfo.keySpec)) ) ssssService.storeSecret( SELF_SIGNING_KEY_SSSS_NAME, session.cryptoService().crossSigningService().getCrossSigningPrivateKeys()!!.selfSigned!!, - listOf(SharedSecretStorageService.KeyRef(keyInfo.keyId, keyInfo.keySpec)) + listOf(KeyRef(keyInfo.keyId, keyInfo.keySpec)) ) ssssService.storeSecret( USER_SIGNING_KEY_SSSS_NAME, session.cryptoService().crossSigningService().getCrossSigningPrivateKeys()!!.user!!, - listOf(SharedSecretStorageService.KeyRef(keyInfo.keyId, keyInfo.keySpec)) + listOf(KeyRef(keyInfo.keyId, keyInfo.keySpec)) ) // set up megolm backup @@ -390,7 +390,7 @@ class CryptoTestHelper(private val testHelper: CommonTestHelper) { ssssService.storeSecret( KEYBACKUP_SECRET_SSSS_NAME, secret, - listOf(SharedSecretStorageService.KeyRef(keyInfo.keyId, keyInfo.keySpec)) + listOf(KeyRef(keyInfo.keyId, keyInfo.keySpec)) ) } } diff --git a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/common/MockOkHttpInterceptor.kt b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/common/MockOkHttpInterceptor.kt index b6d833a77c..6dfee2f18f 100644 --- a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/common/MockOkHttpInterceptor.kt +++ b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/common/MockOkHttpInterceptor.kt @@ -73,9 +73,11 @@ class MockOkHttpInterceptor : TestInterceptor { /** * Simple rule that reply with the given body for any request that matches the match param */ - class SimpleRule(match: String, - private val code: Int = HttpsURLConnection.HTTP_OK, - private val body: String = "{}") : Rule(match) { + class SimpleRule( + match: String, + private val code: Int = HttpsURLConnection.HTTP_OK, + private val body: String = "{}" + ) : Rule(match) { override fun process(originalRequest: Request): Response? { return Response.Builder() diff --git a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/common/RetryTestRule.kt b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/common/RetryTestRule.kt index b16ab98e6c..39f49a9ccc 100644 --- a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/common/RetryTestRule.kt +++ b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/common/RetryTestRule.kt @@ -40,6 +40,9 @@ class RetryTestRule(val retryCount: Int = 3) : TestRule { for (i in 0 until retryCount) { try { base.evaluate() + if (i > 0) { + println("Retried test $i times") + } return } catch (t: Throwable) { caughtThrowable = t diff --git a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/common/TestMatrixCallback.kt b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/common/TestMatrixCallback.kt index 9f6d6eb136..a007d684e3 100644 --- a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/common/TestMatrixCallback.kt +++ b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/common/TestMatrixCallback.kt @@ -27,8 +27,10 @@ import java.util.concurrent.CountDownLatch * @param onlySuccessful true to fail if an error occurs. This is the default behavior * @param */ -open class TestMatrixCallback(private val countDownLatch: CountDownLatch, - private val onlySuccessful: Boolean = true) : MatrixCallback { +open class TestMatrixCallback( + private val countDownLatch: CountDownLatch, + private val onlySuccessful: Boolean = true +) : MatrixCallback { @CallSuper override fun onSuccess(data: T) { diff --git a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/common/TestMatrixComponent.kt b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/common/TestMatrixComponent.kt index 525e168cf1..6cf01d4ae2 100644 --- a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/common/TestMatrixComponent.kt +++ b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/common/TestMatrixComponent.kt @@ -20,7 +20,9 @@ import android.content.Context import dagger.BindsInstance import dagger.Component import org.matrix.android.sdk.api.MatrixConfiguration +import org.matrix.android.sdk.api.securestorage.SecureStorageModule import org.matrix.android.sdk.internal.auth.AuthModule +import org.matrix.android.sdk.internal.debug.DebugModule import org.matrix.android.sdk.internal.di.MatrixComponent import org.matrix.android.sdk.internal.di.MatrixModule import org.matrix.android.sdk.internal.di.MatrixScope @@ -36,8 +38,10 @@ import org.matrix.android.sdk.internal.util.system.SystemModule NetworkModule::class, AuthModule::class, RawModule::class, + DebugModule::class, SettingsModule::class, - SystemModule::class + SystemModule::class, + SecureStorageModule::class, ] ) @MatrixScope @@ -47,7 +51,9 @@ internal interface TestMatrixComponent : MatrixComponent { @Component.Factory interface Factory { - fun create(@BindsInstance context: Context, - @BindsInstance matrixConfiguration: MatrixConfiguration): TestMatrixComponent + fun create( + @BindsInstance context: Context, + @BindsInstance matrixConfiguration: MatrixConfiguration, + ): TestMatrixComponent } } 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 f5f585a1e0..17c7c28d81 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 @@ -22,6 +22,7 @@ import androidx.test.ext.junit.runners.AndroidJUnit4 import org.junit.Assert.assertEquals import org.junit.Assert.assertNotEquals import org.junit.FixMethodOrder +import org.junit.Ignore import org.junit.Test import org.junit.runner.RunWith import org.junit.runners.MethodSorters @@ -40,6 +41,7 @@ import java.util.UUID @Suppress("SpellCheckingInspection") @RunWith(AndroidJUnit4::class) @FixMethodOrder(MethodSorters.NAME_ASCENDING) +@Ignore class AttachmentEncryptionTest { private fun checkDecryption(input: String, encryptedFileInfo: EncryptedFileInfo): String { 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 cd6c146f03..dbc6929e34 100644 --- a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/CryptoStoreTest.kt +++ b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/CryptoStoreTest.kt @@ -22,6 +22,7 @@ import org.junit.Assert.assertEquals import org.junit.Assert.assertNotEquals import org.junit.Assert.assertNull import org.junit.Before +import org.junit.Ignore import org.junit.Rule import org.junit.Test import org.junit.runner.RunWith @@ -37,6 +38,7 @@ import org.matrix.olm.OlmSession private const val DUMMY_DEVICE_KEY = "DeviceKey" @RunWith(AndroidJUnit4::class) +@Ignore class CryptoStoreTest : InstrumentedTest { @get:Rule val rule = RetryTestRule(3) diff --git a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/DecryptRedactedEventTest.kt b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/DecryptRedactedEventTest.kt new file mode 100644 index 0000000000..a48b45a1f5 --- /dev/null +++ b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/DecryptRedactedEventTest.kt @@ -0,0 +1,75 @@ +/* + * Copyright 2022 The Matrix.org Foundation C.I.C. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.matrix.android.sdk.internal.crypto + +import androidx.test.ext.junit.runners.AndroidJUnit4 +import org.junit.Assert +import org.junit.FixMethodOrder +import org.junit.Test +import org.junit.runner.RunWith +import org.junit.runners.MethodSorters +import org.matrix.android.sdk.InstrumentedTest +import org.matrix.android.sdk.api.session.events.model.Event +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.Companion.runCryptoTest + +@RunWith(AndroidJUnit4::class) +@FixMethodOrder(MethodSorters.JVM) +class DecryptRedactedEventTest : InstrumentedTest { + + @Test + fun doNotFailToDecryptRedactedEvent() = runCryptoTest(context()) { cryptoTestHelper, testHelper -> + val testData = cryptoTestHelper.doE2ETestWithAliceAndBobInARoom(true) + val e2eRoomID = testData.roomId + val aliceSession = testData.firstSession + val bobSession = testData.secondSession!! + + val roomALicePOV = aliceSession.getRoom(e2eRoomID)!! + val timelineEvent = testHelper.sendTextMessage(roomALicePOV, "Hello", 1).first() + val redactionReason = "Wrong Room" + roomALicePOV.sendService().redactEvent(timelineEvent.root, redactionReason) + + // get the event from bob + testHelper.waitWithLatch { + testHelper.retryPeriodicallyWithLatch(it) { + bobSession.getRoom(e2eRoomID)?.getTimelineEvent(timelineEvent.eventId)?.root?.isRedacted() == true + } + } + + val eventBobPov = bobSession.getRoom(e2eRoomID)?.getTimelineEvent(timelineEvent.eventId)!! + + testHelper.runBlockingTest { + try { + val result = bobSession.cryptoService().decryptEvent(eventBobPov.root, "") + Assert.assertEquals( + "Unexpected redacted reason", + redactionReason, + result.clearEvent.toModel()?.unsignedData?.redactedEvent?.content?.get("reason") + ) + Assert.assertEquals( + "Unexpected Redacted event id", + timelineEvent.eventId, + result.clearEvent.toModel()?.unsignedData?.redactedEvent?.redacts + ) + } catch (failure: Throwable) { + Assert.fail("Should not throw when decrypting a redacted event") + } + } + } +} 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 552936971f..5a61eee7fe 100644 --- a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/E2eeSanityTests.kt +++ b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/E2eeSanityTests.kt @@ -23,6 +23,7 @@ import org.amshove.kluent.fail import org.amshove.kluent.internal.assertEquals import org.junit.Assert import org.junit.FixMethodOrder +import org.junit.Ignore import org.junit.Rule import org.junit.Test import org.junit.runner.RunWith @@ -57,7 +58,8 @@ import org.matrix.android.sdk.api.session.room.model.message.MessageContent import org.matrix.android.sdk.api.session.room.send.SendState import org.matrix.android.sdk.api.session.room.timeline.TimelineSettings import org.matrix.android.sdk.common.CommonTestHelper -import org.matrix.android.sdk.common.CryptoTestHelper +import org.matrix.android.sdk.common.CommonTestHelper.Companion.runCryptoTest +import org.matrix.android.sdk.common.CommonTestHelper.Companion.runSessionTest import org.matrix.android.sdk.common.RetryTestRule import org.matrix.android.sdk.common.SessionTestParams import org.matrix.android.sdk.common.TestConstants @@ -68,6 +70,7 @@ import java.util.concurrent.CountDownLatch @RunWith(JUnit4::class) @FixMethodOrder(MethodSorters.JVM) @LargeTest +@Ignore("This test fails with an unhandled exception thrown from a coroutine which terminates the entire test run.") class E2eeSanityTests : InstrumentedTest { @get:Rule val rule = RetryTestRule(3) @@ -82,9 +85,7 @@ class E2eeSanityTests : InstrumentedTest { * Alice sends a new message, then check that the new one can be decrypted */ @Test - fun testSendingE2EEMessages() { - val testHelper = CommonTestHelper(context()) - val cryptoTestHelper = CryptoTestHelper(testHelper) + fun testSendingE2EEMessages() = runCryptoTest(context()) { cryptoTestHelper, testHelper -> val cryptoTestData = cryptoTestHelper.doE2ETestWithAliceAndBobInARoom(true) val aliceSession = cryptoTestData.firstSession @@ -198,21 +199,12 @@ class E2eeSanityTests : InstrumentedTest { } } } - - otherAccounts.forEach { - testHelper.signOutAndClose(it) - } - newAccount.forEach { testHelper.signOutAndClose(it) } - - cryptoTestData.cleanUp(testHelper) } @Test - fun testKeyGossipingIsEnabledByDefault() { - val testHelper = CommonTestHelper(context()) + fun testKeyGossipingIsEnabledByDefault() = runSessionTest(context()) { testHelper -> val session = testHelper.createAccount("alice", SessionTestParams(true)) Assert.assertTrue("Key gossiping should be enabled by default", session.cryptoService().isKeyGossipingEnabled()) - testHelper.signOutAndClose(session) } /** @@ -230,9 +222,7 @@ class E2eeSanityTests : InstrumentedTest { * 9. Check that new session can decrypt */ @Test - fun testBasicBackupImport() { - val testHelper = CommonTestHelper(context()) - val cryptoTestHelper = CryptoTestHelper(testHelper) + fun testBasicBackupImport() = runCryptoTest(context()) { cryptoTestHelper, testHelper -> val cryptoTestData = cryptoTestHelper.doE2ETestWithAliceAndBobInARoom(true) val aliceSession = cryptoTestData.firstSession @@ -344,8 +334,6 @@ class E2eeSanityTests : InstrumentedTest { // ensure bob can now decrypt cryptoTestHelper.ensureCanDecrypt(sentEventIds, newBobSession, e2eRoomID, messagesText) - - testHelper.signOutAndClose(newBobSession) } /** @@ -353,9 +341,7 @@ class E2eeSanityTests : InstrumentedTest { * get them from an older one. */ @Test - fun testSimpleGossip() { - val testHelper = CommonTestHelper(context()) - val cryptoTestHelper = CryptoTestHelper(testHelper) + fun testSimpleGossip() = runCryptoTest(context()) { cryptoTestHelper, testHelper -> val cryptoTestData = cryptoTestHelper.doE2ETestWithAliceAndBobInARoom(true) val aliceSession = cryptoTestData.firstSession @@ -449,18 +435,13 @@ class E2eeSanityTests : InstrumentedTest { } cryptoTestHelper.ensureCanDecrypt(sentEventIds, newBobSession, e2eRoomID, messagesText) - - cryptoTestData.cleanUp(testHelper) - testHelper.signOutAndClose(newBobSession) } /** * Test that if a better key is forwarded (lower index, it is then used) */ @Test - fun testForwardBetterKey() { - val testHelper = CommonTestHelper(context()) - val cryptoTestHelper = CryptoTestHelper(testHelper) + fun testForwardBetterKey() = runCryptoTest(context()) { cryptoTestHelper, testHelper -> val cryptoTestData = cryptoTestHelper.doE2ETestWithAliceAndBobInARoom(true) val aliceSession = cryptoTestData.firstSession @@ -576,10 +557,6 @@ class E2eeSanityTests : InstrumentedTest { canDecryptFirst && canDecryptSecond } } - - testHelper.signOutAndClose(aliceSession) - testHelper.signOutAndClose(bobSessionWithBetterKey) - testHelper.signOutAndClose(newBobSession) } private fun sendMessageInRoom(testHelper: CommonTestHelper, aliceRoomPOV: Room, text: String): String? { @@ -610,9 +587,7 @@ class E2eeSanityTests : InstrumentedTest { * Test that if a better key is forwared (lower index, it is then used) */ @Test - fun testSelfInteractiveVerificationAndGossip() { - val testHelper = CommonTestHelper(context()) - val cryptoTestHelper = CryptoTestHelper(testHelper) + fun testASelfInteractiveVerificationAndGossip() = runCryptoTest(context()) { cryptoTestHelper, testHelper -> val aliceSession = testHelper.createAccount("alice", SessionTestParams(true)) cryptoTestHelper.bootstrapSecurity(aliceSession) @@ -651,7 +626,7 @@ class E2eeSanityTests : InstrumentedTest { // we can release this latch? oldCompleteLatch.countDown() } - else -> Unit + else -> Unit } } }) @@ -678,17 +653,17 @@ class E2eeSanityTests : InstrumentedTest { IncomingSasVerificationTransaction.UxState.SHOW_ACCEPT -> { // no need to accept as there was a request first it will auto accept } - IncomingSasVerificationTransaction.UxState.SHOW_SAS -> { + IncomingSasVerificationTransaction.UxState.SHOW_SAS -> { if (matchOnce) { sasTx.userHasVerifiedShortCode() newCode = sasTx.getDecimalCodeRepresentation() matchOnce = false } } - IncomingSasVerificationTransaction.UxState.VERIFIED -> { + IncomingSasVerificationTransaction.UxState.VERIFIED -> { newCompleteLatch.countDown() } - else -> Unit + else -> Unit } } }) @@ -751,9 +726,6 @@ class E2eeSanityTests : InstrumentedTest { aliceSession.cryptoService().keysBackupService().getKeyBackupRecoveryKeyInfo()!!.version, aliceNewSession.cryptoService().keysBackupService().getKeyBackupRecoveryKeyInfo()!!.version ) - - testHelper.signOutAndClose(aliceSession) - testHelper.signOutAndClose(aliceNewSession) } private fun ensureMembersHaveJoined(testHelper: CommonTestHelper, aliceSession: Session, otherAccounts: List, e2eRoomID: String) { diff --git a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/ExportEncryptionTest.kt b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/ExportEncryptionTest.kt index c2d8f4fb35..bddb31fc92 100644 --- a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/ExportEncryptionTest.kt +++ b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/ExportEncryptionTest.kt @@ -21,6 +21,7 @@ import org.junit.Assert.assertEquals import org.junit.Assert.assertTrue import org.junit.Assert.fail import org.junit.FixMethodOrder +import org.junit.Ignore import org.junit.Test import org.junit.runner.RunWith import org.junit.runners.MethodSorters @@ -30,6 +31,7 @@ import org.junit.runners.MethodSorters */ @RunWith(AndroidJUnit4::class) @FixMethodOrder(MethodSorters.NAME_ASCENDING) +@Ignore class ExportEncryptionTest { @Test @@ -83,7 +85,8 @@ class ExportEncryptionTest { @Test fun checkExportDecrypt1() { val password = "password" - val input = "-----BEGIN MEGOLM SESSION DATA-----\nAXNhbHRzYWx0c2FsdHNhbHSIiIiIiIiIiIiIiIiIiIiIAAAACmIRUW2OjZ3L2l6j9h0lHlV3M2dx\n" + "cissyYBxjsfsAndErh065A8=\n-----END MEGOLM SESSION DATA-----" + val input = "-----BEGIN MEGOLM SESSION DATA-----\nAXNhbHRzYWx0c2FsdHNhbHSIiIiIiIiIiIiIiIiIiIiIAAAACmIRUW2OjZ3L2l6j9h0lHlV3M2dx\n" + + "cissyYBxjsfsAndErh065A8=\n-----END MEGOLM SESSION DATA-----" val expectedString = "plain" var decodedString: String? = null @@ -103,7 +106,8 @@ class ExportEncryptionTest { @Test fun checkExportDecrypt2() { val password = "betterpassword" - val input = "-----BEGIN MEGOLM SESSION DATA-----\nAW1vcmVzYWx0bW9yZXNhbHT//////////wAAAAAAAAAAAAAD6KyBpe1Niv5M5NPm4ZATsJo5nghk\n" + "KYu63a0YQ5DRhUWEKk7CcMkrKnAUiZny\n-----END MEGOLM SESSION DATA-----" + val input = "-----BEGIN MEGOLM SESSION DATA-----\nAW1vcmVzYWx0bW9yZXNhbHT//////////wAAAAAAAAAAAAAD6KyBpe1Niv5M5NPm4ZATsJo5nghk\n" + + "KYu63a0YQ5DRhUWEKk7CcMkrKnAUiZny\n-----END MEGOLM SESSION DATA-----" val expectedString = "Hello, World" var decodedString: String? = null @@ -123,7 +127,8 @@ class ExportEncryptionTest { @Test fun checkExportDecrypt3() { val password = "SWORDFISH" - val input = "-----BEGIN MEGOLM SESSION DATA-----\nAXllc3NhbHR5Z29vZG5lc3P//////////wAAAAAAAAAAAAAD6OIW+Je7gwvjd4kYrb+49gKCfExw\n" + "MgJBMD4mrhLkmgAngwR1pHjbWXaoGybtiAYr0moQ93GrBQsCzPbvl82rZhaXO3iH5uHo/RCEpOqp\nPgg29363BGR+/Ripq/VCLKGNbw==\n-----END MEGOLM SESSION DATA-----" + val input = "-----BEGIN MEGOLM SESSION DATA-----\nAXllc3NhbHR5Z29vZG5lc3P//////////wAAAAAAAAAAAAAD6OIW+Je7gwvjd4kYrb+49gKCfExw\n" + + "MgJBMD4mrhLkmgAngwR1pHjbWXaoGybtiAYr0moQ93GrBQsCzPbvl82rZhaXO3iH5uHo/RCEpOqp\nPgg29363BGR+/Ripq/VCLKGNbw==\n-----END MEGOLM SESSION DATA-----" val expectedString = "alphanumericallyalphanumericallyalphanumericallyalphanumerically" var decodedString: String? = null @@ -202,7 +207,8 @@ class ExportEncryptionTest { @Test fun checkExportEncrypt4() { - val password = "passwordpasswordpasswordpasswordpasswordpasswordpasswordpasswordpasswordpasswordpasswordpasswordpasswordpasswordpasswordpassword" + "passwordpasswordpasswordpasswordpasswordpasswordpasswordpasswordpasswordpasswordpasswordpasswordpasswordpasswordpasswordpassword" + val password = "passwordpasswordpasswordpasswordpasswordpasswordpasswordpasswordpasswordpasswordpasswordpasswordpasswordpasswordpasswordpassword" + + "passwordpasswordpasswordpasswordpasswordpasswordpasswordpasswordpasswordpasswordpasswordpasswordpasswordpasswordpasswordpassword" val expectedString = "alphanumericallyalphanumericallyalphanumericallyalphanumerically" var decodedString: String? = null 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 93aa78a305..e37ae5be86 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 @@ -30,18 +30,14 @@ import org.matrix.android.sdk.api.session.events.model.content.EncryptedEventCon 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.CommonTestHelper.Companion.runCryptoTest @RunWith(AndroidJUnit4::class) @FixMethodOrder(MethodSorters.JVM) class PreShareKeysTest : InstrumentedTest { - private val testHelper = CommonTestHelper(context()) - private val cryptoTestHelper = CryptoTestHelper(testHelper) - @Test - fun ensure_outbound_session_happy_path() { + fun ensure_outbound_session_happy_path() = runCryptoTest(context()) { cryptoTestHelper, testHelper -> val testData = cryptoTestHelper.doE2ETestWithAliceAndBobInARoom(true) val e2eRoomID = testData.roomId val aliceSession = testData.firstSession @@ -94,7 +90,5 @@ class PreShareKeysTest : InstrumentedTest { bobSession.getRoom(e2eRoomID)?.getTimelineEvent(sentEvent.eventId)?.root?.getClearType() == EventType.MESSAGE } } - - testData.cleanUp(testHelper) } } 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 0f3a4b4181..e8a474a54a 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 @@ -21,6 +21,7 @@ import org.amshove.kluent.shouldBe import org.junit.Assert import org.junit.Before import org.junit.FixMethodOrder +import org.junit.Ignore import org.junit.Test import org.junit.runner.RunWith import org.junit.runners.MethodSorters @@ -38,8 +39,7 @@ 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 -import org.matrix.android.sdk.common.CommonTestHelper -import org.matrix.android.sdk.common.CryptoTestHelper +import org.matrix.android.sdk.common.CommonTestHelper.Companion.runCryptoTest import org.matrix.android.sdk.common.TestConstants import org.matrix.android.sdk.internal.crypto.model.OlmSessionWrapper import org.matrix.android.sdk.internal.crypto.store.db.deserializeFromRealm @@ -60,11 +60,10 @@ import kotlin.coroutines.resume */ @RunWith(AndroidJUnit4::class) @FixMethodOrder(MethodSorters.JVM) +@Ignore class UnwedgingTest : InstrumentedTest { private lateinit var messagesReceivedByBob: List - private val testHelper = CommonTestHelper(context()) - private val cryptoTestHelper = CryptoTestHelper(testHelper) @Before fun init() { @@ -85,7 +84,7 @@ class UnwedgingTest : InstrumentedTest { * -> This is automatically fixed after SDKs restarted the olm session */ @Test - fun testUnwedging() { + fun testUnwedging() = runCryptoTest(context()) { cryptoTestHelper, testHelper -> val cryptoTestData = cryptoTestHelper.doE2ETestWithAliceAndBobInARoom() val aliceSession = cryptoTestData.firstSession @@ -240,8 +239,6 @@ class UnwedgingTest : InstrumentedTest { } bobTimeline.dispose() - - cryptoTestData.cleanUp(testHelper) } private fun createEventListener(latch: CountDownLatch, expectedNumberOfMessages: Int): Timeline.Listener { 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 abcf1714b8..8cb38ddc87 100644 --- a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/crosssigning/XSigningTest.kt +++ b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/crosssigning/XSigningTest.kt @@ -25,6 +25,7 @@ import org.junit.Assert.assertNull import org.junit.Assert.assertTrue import org.junit.Assert.fail import org.junit.FixMethodOrder +import org.junit.Ignore import org.junit.Test import org.junit.runner.RunWith import org.junit.runners.MethodSorters @@ -37,8 +38,8 @@ import org.matrix.android.sdk.api.session.crypto.crosssigning.isCrossSignedVerif import org.matrix.android.sdk.api.session.crypto.crosssigning.isVerified 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.common.CommonTestHelper -import org.matrix.android.sdk.common.CryptoTestHelper +import org.matrix.android.sdk.common.CommonTestHelper.Companion.runCryptoTest +import org.matrix.android.sdk.common.CommonTestHelper.Companion.runSessionTest import org.matrix.android.sdk.common.SessionTestParams import org.matrix.android.sdk.common.TestConstants import kotlin.coroutines.Continuation @@ -47,13 +48,11 @@ import kotlin.coroutines.resume @RunWith(AndroidJUnit4::class) @FixMethodOrder(MethodSorters.NAME_ASCENDING) @LargeTest +@Ignore class XSigningTest : InstrumentedTest { - private val testHelper = CommonTestHelper(context()) - private val cryptoTestHelper = CryptoTestHelper(testHelper) - @Test - fun test_InitializeAndStoreKeys() { + fun test_InitializeAndStoreKeys() = runSessionTest(context()) { testHelper -> val aliceSession = testHelper.createAccount(TestConstants.USER_ALICE, SessionTestParams(true)) testHelper.doSync { @@ -87,7 +86,7 @@ class XSigningTest : InstrumentedTest { } @Test - fun test_CrossSigningCheckBobSeesTheKeys() { + fun test_CrossSigningCheckBobSeesTheKeys() = runCryptoTest(context()) { cryptoTestHelper, testHelper -> val cryptoTestData = cryptoTestHelper.doE2ETestWithAliceAndBobInARoom() val aliceSession = cryptoTestData.firstSession @@ -137,12 +136,10 @@ class XSigningTest : InstrumentedTest { ) assertFalse("Bob keys from alice pov should not be trusted", bobKeysFromAlicePOV.isTrusted()) - - cryptoTestData.cleanUp(testHelper) } @Test - fun test_CrossSigningTestAliceTrustBobNewDevice() { + fun test_CrossSigningTestAliceTrustBobNewDevice() = runCryptoTest(context()) { cryptoTestHelper, testHelper -> val cryptoTestData = cryptoTestHelper.doE2ETestWithAliceAndBobInARoom() val aliceSession = cryptoTestData.firstSession @@ -216,9 +213,5 @@ class XSigningTest : InstrumentedTest { val result = aliceSession.cryptoService().crossSigningService().checkDeviceTrust(bobUserId, bobSecondDeviceId, null) assertTrue("Bob second device should be trusted from alice POV", result.isCrossSignedVerified()) - - testHelper.signOutAndClose(aliceSession) - testHelper.signOutAndClose(bobSession) - testHelper.signOutAndClose(bobSession2) } } 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 85b6c21df3..5f26fda946 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 @@ -35,6 +35,7 @@ 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 import org.matrix.android.sdk.common.CommonTestHelper +import org.matrix.android.sdk.common.CommonTestHelper.Companion.runCryptoTest import org.matrix.android.sdk.common.CryptoTestHelper import java.util.concurrent.CountDownLatch @@ -42,35 +43,36 @@ import java.util.concurrent.CountDownLatch @FixMethodOrder(MethodSorters.NAME_ASCENDING) class EncryptionTest : InstrumentedTest { - private val testHelper = CommonTestHelper(context()) - private val cryptoTestHelper = CryptoTestHelper(testHelper) - @Test fun test_EncryptionEvent() { - performTest(roomShouldBeEncrypted = false) { room -> - // Send an encryption Event as an Event (and not as a state event) - room.sendService().sendEvent( - eventType = EventType.STATE_ROOM_ENCRYPTION, - content = EncryptionEventContent(algorithm = MXCRYPTO_ALGORITHM_MEGOLM).toContent() - ) - } - } - - @Test - fun test_EncryptionStateEvent() { - performTest(roomShouldBeEncrypted = true) { room -> - runBlocking { - // Send an encryption Event as a State Event - room.stateService().sendStateEvent( + runCryptoTest(context()) { cryptoTestHelper, testHelper -> + performTest(cryptoTestHelper, testHelper, roomShouldBeEncrypted = false) { room -> + // Send an encryption Event as an Event (and not as a state event) + room.sendService().sendEvent( eventType = EventType.STATE_ROOM_ENCRYPTION, - stateKey = "", - body = EncryptionEventContent(algorithm = MXCRYPTO_ALGORITHM_MEGOLM).toContent() + content = EncryptionEventContent(algorithm = MXCRYPTO_ALGORITHM_MEGOLM).toContent() ) } } } - private fun performTest(roomShouldBeEncrypted: Boolean, action: (Room) -> Unit) { + @Test + fun test_EncryptionStateEvent() { + runCryptoTest(context()) { cryptoTestHelper, testHelper -> + performTest(cryptoTestHelper, testHelper, roomShouldBeEncrypted = true) { room -> + runBlocking { + // Send an encryption Event as a State Event + room.stateService().sendStateEvent( + eventType = EventType.STATE_ROOM_ENCRYPTION, + stateKey = "", + body = EncryptionEventContent(algorithm = MXCRYPTO_ALGORITHM_MEGOLM).toContent() + ) + } + } + } + } + + private fun performTest(cryptoTestHelper: CryptoTestHelper, testHelper: CommonTestHelper, roomShouldBeEncrypted: Boolean, action: (Room) -> Unit) { val cryptoTestData = cryptoTestHelper.doE2ETestWithAliceInARoom(encryptedRoom = false) val aliceSession = cryptoTestData.firstSession @@ -109,6 +111,5 @@ class EncryptionTest : InstrumentedTest { 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 895f95aeac..7bb53e139c 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 @@ -25,6 +25,7 @@ import org.amshove.kluent.internal.assertEquals import org.junit.Assert import org.junit.Assert.assertNull import org.junit.FixMethodOrder +import org.junit.Ignore import org.junit.Rule import org.junit.Test import org.junit.runner.RunWith @@ -41,8 +42,7 @@ 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.timeline.getLastMessageContent -import org.matrix.android.sdk.common.CommonTestHelper -import org.matrix.android.sdk.common.CryptoTestHelper +import org.matrix.android.sdk.common.CommonTestHelper.Companion.runCryptoTest import org.matrix.android.sdk.common.RetryTestRule import org.matrix.android.sdk.common.SessionTestParams import org.matrix.android.sdk.common.TestConstants @@ -51,14 +51,13 @@ import org.matrix.android.sdk.mustFail @RunWith(AndroidJUnit4::class) @FixMethodOrder(MethodSorters.JVM) @LargeTest +@Ignore class KeyShareTests : InstrumentedTest { @get:Rule val rule = RetryTestRule(3) @Test - fun test_DoNotSelfShareIfNotTrusted() { - val commonTestHelper = CommonTestHelper(context()) - val cryptoTestHelper = CryptoTestHelper(commonTestHelper) + fun test_DoNotSelfShareIfNotTrusted() = runCryptoTest(context()) { cryptoTestHelper, commonTestHelper -> val aliceSession = commonTestHelper.createAccount(TestConstants.USER_ALICE, SessionTestParams(true)) Log.v("TEST", "=======> AliceSession 1 is ${aliceSession.sessionParams.deviceId}") @@ -194,9 +193,7 @@ class KeyShareTests : InstrumentedTest { * if the key was originally shared with him */ @Test - fun test_reShareIfWasIntendedToBeShared() { - val commonTestHelper = CommonTestHelper(context()) - val cryptoTestHelper = CryptoTestHelper(commonTestHelper) + fun test_reShareIfWasIntendedToBeShared() = runCryptoTest(context()) { cryptoTestHelper, commonTestHelper -> val testData = cryptoTestHelper.doE2ETestWithAliceAndBobInARoom(true) val aliceSession = testData.firstSession @@ -227,9 +224,7 @@ class KeyShareTests : InstrumentedTest { * if the key was originally shared with him */ @Test - fun test_reShareToUnverifiedIfWasIntendedToBeShared() { - val commonTestHelper = CommonTestHelper(context()) - val cryptoTestHelper = CryptoTestHelper(commonTestHelper) + fun test_reShareToUnverifiedIfWasIntendedToBeShared() = runCryptoTest(context()) { cryptoTestHelper, commonTestHelper -> val testData = cryptoTestHelper.doE2ETestWithAliceInARoom(true) val aliceSession = testData.firstSession @@ -266,9 +261,7 @@ class KeyShareTests : InstrumentedTest { * Tests that keys reshared with own verified session are done from the earliest known index */ @Test - fun test_reShareFromTheEarliestKnownIndexWithOwnVerifiedSession() { - val commonTestHelper = CommonTestHelper(context()) - val cryptoTestHelper = CryptoTestHelper(commonTestHelper) + fun test_reShareFromTheEarliestKnownIndexWithOwnVerifiedSession() = runCryptoTest(context()) { cryptoTestHelper, commonTestHelper -> val testData = cryptoTestHelper.doE2ETestWithAliceAndBobInARoom(true) val aliceSession = testData.firstSession @@ -388,10 +381,7 @@ class KeyShareTests : InstrumentedTest { * Tests that we don't cancel a request to early on first forward if the index is not good enough */ @Test - fun test_dontCancelToEarly() { - val commonTestHelper = CommonTestHelper(context()) - val cryptoTestHelper = CryptoTestHelper(commonTestHelper) - + fun test_dontCancelToEarly() = runCryptoTest(context()) { cryptoTestHelper, commonTestHelper -> val testData = cryptoTestHelper.doE2ETestWithAliceAndBobInARoom(true) val aliceSession = testData.firstSession val bobSession = testData.secondSession!! @@ -434,7 +424,7 @@ class KeyShareTests : InstrumentedTest { // /!\ Stop initial alice session syncing so that it can't reply aliceSession.cryptoService().enableKeyGossiping(false) - aliceSession.stopSync() + aliceSession.syncService().stopSync() // Let's now try to request aliceNewSession.cryptoService().reRequestRoomKeyForEvent(sentEvents.first().root) @@ -442,7 +432,7 @@ class KeyShareTests : InstrumentedTest { // Should get a reply from bob and not from alice commonTestHelper.waitWithLatch { latch -> commonTestHelper.retryPeriodicallyWithLatch(latch) { - // Log.d("#TEST", "outgoing key requests :${aliceNewSession.cryptoService().getOutgoingRoomKeyRequests().joinToString { it.sessionId ?: "?" }}") + // Log.d("#TEST", "outgoing key requests :${aliceNewSession.cryptoService().getOutgoingRoomKeyRequests().joinToString { it.sessionId ?: "?" }}") val outgoing = aliceNewSession.cryptoService().getOutgoingRoomKeyRequests().firstOrNull { it.sessionId == sentEventMegolmSession } val bobReply = outgoing?.results?.firstOrNull { it.userId == bobSession.myUserId } val result = bobReply?.result @@ -457,7 +447,7 @@ class KeyShareTests : InstrumentedTest { // let's wake up alice aliceSession.cryptoService().enableKeyGossiping(true) - aliceSession.startSync(true) + aliceSession.syncService().startSync(true) // We should now get a reply from first session commonTestHelper.waitWithLatch { latch -> 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 13133b726c..ae420a09b3 100644 --- a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/gossiping/WithHeldTests.kt +++ b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/gossiping/WithHeldTests.kt @@ -21,6 +21,7 @@ import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.LargeTest import org.junit.Assert import org.junit.FixMethodOrder +import org.junit.Ignore import org.junit.Rule import org.junit.Test import org.junit.runner.RunWith @@ -36,8 +37,7 @@ 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.CommonTestHelper.Companion.runCryptoTest import org.matrix.android.sdk.common.MockOkHttpInterceptor import org.matrix.android.sdk.common.RetryTestRule import org.matrix.android.sdk.common.SessionTestParams @@ -47,14 +47,13 @@ import org.matrix.android.sdk.mustFail @RunWith(AndroidJUnit4::class) @FixMethodOrder(MethodSorters.JVM) @LargeTest +@Ignore class WithHeldTests : InstrumentedTest { @get:Rule val rule = RetryTestRule(3) @Test - fun test_WithHeldUnverifiedReason() { - val testHelper = CommonTestHelper(context()) - val cryptoTestHelper = CryptoTestHelper(testHelper) + fun test_WithHeldUnverifiedReason() = runCryptoTest(context()) { cryptoTestHelper, testHelper -> // ============================= // ARRANGE @@ -153,16 +152,10 @@ class WithHeldTests : InstrumentedTest { bobUnverifiedSession.cryptoService().decryptEvent(eventBobPOV.root, "") } } - - testHelper.signOutAndClose(aliceSession) - testHelper.signOutAndClose(bobSession) - testHelper.signOutAndClose(bobUnverifiedSession) } @Test - fun test_WithHeldNoOlm() { - val testHelper = CommonTestHelper(context()) - val cryptoTestHelper = CryptoTestHelper(testHelper) + fun test_WithHeldNoOlm() = runCryptoTest(context()) { cryptoTestHelper, testHelper -> val testData = cryptoTestHelper.doE2ETestWithAliceAndBobInARoom() val aliceSession = testData.firstSession @@ -239,14 +232,10 @@ class WithHeldTests : InstrumentedTest { Assert.assertEquals("Alice should have marked bob's device for this session", 1, chainIndex2) aliceInterceptor.clearRules() - testData.cleanUp(testHelper) - testHelper.signOutAndClose(bobSecondSession) } @Test - fun test_WithHeldKeyRequest() { - val testHelper = CommonTestHelper(context()) - val cryptoTestHelper = CryptoTestHelper(testHelper) + fun test_WithHeldKeyRequest() = runCryptoTest(context()) { cryptoTestHelper, testHelper -> val testData = cryptoTestHelper.doE2ETestWithAliceAndBobInARoom() val aliceSession = testData.firstSession @@ -293,8 +282,5 @@ class WithHeldTests : InstrumentedTest { wc?.code == WithHeldCode.UNAUTHORISED } } - - testHelper.signOutAndClose(aliceSession) - testHelper.signOutAndClose(bobSecondSession) } } diff --git a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/keysbackup/KeysBackupTest.kt b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/keysbackup/KeysBackupTest.kt index 9136272b1e..fb498e0de5 100644 --- a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/keysbackup/KeysBackupTest.kt +++ b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/keysbackup/KeysBackupTest.kt @@ -24,6 +24,8 @@ import org.junit.Assert.assertNotNull import org.junit.Assert.assertNull import org.junit.Assert.assertTrue import org.junit.FixMethodOrder +import org.junit.Ignore +import org.junit.Rule import org.junit.Test import org.junit.runner.RunWith import org.junit.runners.MethodSorters @@ -43,8 +45,9 @@ import org.matrix.android.sdk.api.session.crypto.keysbackup.MegolmBackupCreation 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.CommonTestHelper.Companion.runCryptoTest +import org.matrix.android.sdk.common.CommonTestHelper.Companion.runSessionTest +import org.matrix.android.sdk.common.RetryTestRule import org.matrix.android.sdk.common.TestConstants import org.matrix.android.sdk.common.TestMatrixCallback import java.util.Collections @@ -53,17 +56,18 @@ import java.util.concurrent.CountDownLatch @RunWith(AndroidJUnit4::class) @FixMethodOrder(MethodSorters.JVM) @LargeTest +@Ignore class KeysBackupTest : InstrumentedTest { + @get:Rule val rule = RetryTestRule(3) + /** * - From doE2ETestWithAliceAndBobInARoomWithEncryptedMessages, we should have no backed up keys * - Check backup keys after having marked one as backed up * - Reset keys backup markers */ @Test - fun roomKeysTest_testBackupStore_ok() { - val testHelper = CommonTestHelper(context()) - val cryptoTestHelper = CryptoTestHelper(testHelper) + fun roomKeysTest_testBackupStore_ok() = runCryptoTest(context()) { cryptoTestHelper, testHelper -> val cryptoTestData = cryptoTestHelper.doE2ETestWithAliceAndBobInARoomWithEncryptedMessages() @@ -102,8 +106,7 @@ class KeysBackupTest : InstrumentedTest { * Check that prepareKeysBackupVersionWithPassword returns valid data */ @Test - fun prepareKeysBackupVersionTest() { - val testHelper = CommonTestHelper(context()) + fun prepareKeysBackupVersionTest() = runSessionTest(context()) { testHelper -> val bobSession = testHelper.createAccount(TestConstants.USER_BOB, KeysBackupTestConstants.defaultSessionParams) @@ -113,7 +116,7 @@ class KeysBackupTest : InstrumentedTest { val stateObserver = StateObserver(keysBackup) - assertFalse(keysBackup.isEnabled) + assertFalse(keysBackup.isEnabled()) val megolmBackupCreationInfo = testHelper.doSync { keysBackup.prepareKeysBackupVersion(null, null, it) @@ -125,16 +128,13 @@ class KeysBackupTest : InstrumentedTest { assertNotNull(megolmBackupCreationInfo.recoveryKey) stateObserver.stopAndCheckStates(null) - testHelper.signOutAndClose(bobSession) } /** * Test creating a keys backup version and check that createKeysBackupVersion() returns valid data */ @Test - fun createKeysBackupVersionTest() { - val testHelper = CommonTestHelper(context()) - val cryptoTestHelper = CryptoTestHelper(testHelper) + fun createKeysBackupVersionTest() = runCryptoTest(context()) { cryptoTestHelper, testHelper -> val bobSession = testHelper.createAccount(TestConstants.USER_BOB, KeysBackupTestConstants.defaultSessionParams) cryptoTestHelper.initializeCrossSigning(bobSession) @@ -143,13 +143,13 @@ class KeysBackupTest : InstrumentedTest { val stateObserver = StateObserver(keysBackup) - assertFalse(keysBackup.isEnabled) + assertFalse(keysBackup.isEnabled()) val megolmBackupCreationInfo = testHelper.doSync { keysBackup.prepareKeysBackupVersion(null, null, it) } - assertFalse(keysBackup.isEnabled) + assertFalse(keysBackup.isEnabled()) // Create the version val version = testHelper.doSync { @@ -157,7 +157,7 @@ class KeysBackupTest : InstrumentedTest { } // Backup must be enable now - assertTrue(keysBackup.isEnabled) + assertTrue(keysBackup.isEnabled()) // Check that it's signed with MSK val versionResult = testHelper.doSync { @@ -193,7 +193,6 @@ class KeysBackupTest : InstrumentedTest { } stateObserver.stopAndCheckStates(null) - testHelper.signOutAndClose(bobSession) } /** @@ -201,9 +200,7 @@ class KeysBackupTest : InstrumentedTest { * - Check the backup completes */ @Test - fun backupAfterCreateKeysBackupVersionTest() { - val testHelper = CommonTestHelper(context()) - val cryptoTestHelper = CryptoTestHelper(testHelper) + fun backupAfterCreateKeysBackupVersionTest() = runCryptoTest(context()) { cryptoTestHelper, testHelper -> val keysBackupTestHelper = KeysBackupTestHelper(testHelper, cryptoTestHelper) val cryptoTestData = cryptoTestHelper.doE2ETestWithAliceAndBobInARoomWithEncryptedMessages() @@ -238,16 +235,13 @@ class KeysBackupTest : InstrumentedTest { KeysBackupState.ReadyToBackUp ) ) - cryptoTestData.cleanUp(testHelper) } /** * Check that backupAllGroupSessions() returns valid data */ @Test - fun backupAllGroupSessionsTest() { - val testHelper = CommonTestHelper(context()) - val cryptoTestHelper = CryptoTestHelper(testHelper) + fun backupAllGroupSessionsTest() = runCryptoTest(context()) { cryptoTestHelper, testHelper -> val keysBackupTestHelper = KeysBackupTestHelper(testHelper, cryptoTestHelper) val cryptoTestData = cryptoTestHelper.doE2ETestWithAliceAndBobInARoomWithEncryptedMessages() @@ -281,7 +275,6 @@ class KeysBackupTest : InstrumentedTest { assertEquals("All keys must have been marked as backed up", nbOfKeys, backedUpKeys) stateObserver.stopAndCheckStates(null) - cryptoTestData.cleanUp(testHelper) } /** @@ -293,9 +286,7 @@ class KeysBackupTest : InstrumentedTest { * - Compare the decrypted megolm key with the original one */ @Test - fun testEncryptAndDecryptKeysBackupData() { - val testHelper = CommonTestHelper(context()) - val cryptoTestHelper = CryptoTestHelper(testHelper) + fun testEncryptAndDecryptKeysBackupData() = runCryptoTest(context()) { cryptoTestHelper, testHelper -> val keysBackupTestHelper = KeysBackupTestHelper(testHelper, cryptoTestHelper) val cryptoTestData = cryptoTestHelper.doE2ETestWithAliceAndBobInARoomWithEncryptedMessages() @@ -330,7 +321,6 @@ class KeysBackupTest : InstrumentedTest { keysBackupTestHelper.assertKeysEquals(session.exportKeys(), sessionData) stateObserver.stopAndCheckStates(null) - cryptoTestData.cleanUp(testHelper) } /** @@ -340,9 +330,7 @@ class KeysBackupTest : InstrumentedTest { * - Restore must be successful */ @Test - fun restoreKeysBackupTest() { - val testHelper = CommonTestHelper(context()) - val cryptoTestHelper = CryptoTestHelper(testHelper) + fun restoreKeysBackupTest() = runCryptoTest(context()) { cryptoTestHelper, testHelper -> val keysBackupTestHelper = KeysBackupTestHelper(testHelper, cryptoTestHelper) val testData = keysBackupTestHelper.createKeysBackupScenarioWithPassword(null) @@ -428,9 +416,7 @@ class KeysBackupTest : InstrumentedTest { * - It must be trusted and must have with 2 signatures now */ @Test - fun trustKeyBackupVersionTest() { - val testHelper = CommonTestHelper(context()) - val cryptoTestHelper = CryptoTestHelper(testHelper) + fun trustKeyBackupVersionTest() = runCryptoTest(context()) { cryptoTestHelper, testHelper -> val keysBackupTestHelper = KeysBackupTestHelper(testHelper, cryptoTestHelper) // - Do an e2e backup to the homeserver with a recovery key @@ -441,8 +427,8 @@ class KeysBackupTest : InstrumentedTest { // - The new device must see the previous backup as not trusted assertNotNull(testData.aliceSession2.cryptoService().keysBackupService().keysBackupVersion) - assertFalse(testData.aliceSession2.cryptoService().keysBackupService().isEnabled) - assertEquals(KeysBackupState.NotTrusted, testData.aliceSession2.cryptoService().keysBackupService().state) + assertFalse(testData.aliceSession2.cryptoService().keysBackupService().isEnabled()) + assertEquals(KeysBackupState.NotTrusted, testData.aliceSession2.cryptoService().keysBackupService().getState()) // - Trust the backup from the new device testHelper.doSync { @@ -458,7 +444,7 @@ class KeysBackupTest : InstrumentedTest { // - Backup must be enabled on the new device, on the same version assertEquals(testData.prepareKeysBackupDataResult.version, testData.aliceSession2.cryptoService().keysBackupService().keysBackupVersion?.version) - assertTrue(testData.aliceSession2.cryptoService().keysBackupService().isEnabled) + assertTrue(testData.aliceSession2.cryptoService().keysBackupService().isEnabled()) // - Retrieve the last version from the server val keysVersionResult = testHelper.doSync { @@ -477,7 +463,6 @@ class KeysBackupTest : InstrumentedTest { assertEquals(2, keysBackupVersionTrust.signatures.size) stateObserver.stopAndCheckStates(null) - testData.cleanUp(testHelper) } /** @@ -491,9 +476,7 @@ class KeysBackupTest : InstrumentedTest { * - It must be trusted and must have with 2 signatures now */ @Test - fun trustKeyBackupVersionWithRecoveryKeyTest() { - val testHelper = CommonTestHelper(context()) - val cryptoTestHelper = CryptoTestHelper(testHelper) + fun trustKeyBackupVersionWithRecoveryKeyTest() = runCryptoTest(context()) { cryptoTestHelper, testHelper -> val keysBackupTestHelper = KeysBackupTestHelper(testHelper, cryptoTestHelper) // - Do an e2e backup to the homeserver with a recovery key @@ -504,8 +487,8 @@ class KeysBackupTest : InstrumentedTest { // - The new device must see the previous backup as not trusted assertNotNull(testData.aliceSession2.cryptoService().keysBackupService().keysBackupVersion) - assertFalse(testData.aliceSession2.cryptoService().keysBackupService().isEnabled) - assertEquals(KeysBackupState.NotTrusted, testData.aliceSession2.cryptoService().keysBackupService().state) + assertFalse(testData.aliceSession2.cryptoService().keysBackupService().isEnabled()) + assertEquals(KeysBackupState.NotTrusted, testData.aliceSession2.cryptoService().keysBackupService().getState()) // - Trust the backup from the new device with the recovery key testHelper.doSync { @@ -521,7 +504,7 @@ class KeysBackupTest : InstrumentedTest { // - Backup must be enabled on the new device, on the same version assertEquals(testData.prepareKeysBackupDataResult.version, testData.aliceSession2.cryptoService().keysBackupService().keysBackupVersion?.version) - assertTrue(testData.aliceSession2.cryptoService().keysBackupService().isEnabled) + assertTrue(testData.aliceSession2.cryptoService().keysBackupService().isEnabled()) // - Retrieve the last version from the server val keysVersionResult = testHelper.doSync { @@ -540,7 +523,6 @@ class KeysBackupTest : InstrumentedTest { assertEquals(2, keysBackupVersionTrust.signatures.size) stateObserver.stopAndCheckStates(null) - testData.cleanUp(testHelper) } /** @@ -552,9 +534,7 @@ class KeysBackupTest : InstrumentedTest { * - The backup must still be untrusted and disabled */ @Test - fun trustKeyBackupVersionWithWrongRecoveryKeyTest() { - val testHelper = CommonTestHelper(context()) - val cryptoTestHelper = CryptoTestHelper(testHelper) + fun trustKeyBackupVersionWithWrongRecoveryKeyTest() = runCryptoTest(context()) { cryptoTestHelper, testHelper -> val keysBackupTestHelper = KeysBackupTestHelper(testHelper, cryptoTestHelper) // - Do an e2e backup to the homeserver with a recovery key @@ -565,8 +545,8 @@ class KeysBackupTest : InstrumentedTest { // - The new device must see the previous backup as not trusted assertNotNull(testData.aliceSession2.cryptoService().keysBackupService().keysBackupVersion) - assertFalse(testData.aliceSession2.cryptoService().keysBackupService().isEnabled) - assertEquals(KeysBackupState.NotTrusted, testData.aliceSession2.cryptoService().keysBackupService().state) + assertFalse(testData.aliceSession2.cryptoService().keysBackupService().isEnabled()) + assertEquals(KeysBackupState.NotTrusted, testData.aliceSession2.cryptoService().keysBackupService().getState()) // - Try to trust the backup from the new device with a wrong recovery key val latch = CountDownLatch(1) @@ -579,11 +559,10 @@ class KeysBackupTest : InstrumentedTest { // - The new device must still see the previous backup as not trusted assertNotNull(testData.aliceSession2.cryptoService().keysBackupService().keysBackupVersion) - assertFalse(testData.aliceSession2.cryptoService().keysBackupService().isEnabled) - assertEquals(KeysBackupState.NotTrusted, testData.aliceSession2.cryptoService().keysBackupService().state) + assertFalse(testData.aliceSession2.cryptoService().keysBackupService().isEnabled()) + assertEquals(KeysBackupState.NotTrusted, testData.aliceSession2.cryptoService().keysBackupService().getState()) stateObserver.stopAndCheckStates(null) - testData.cleanUp(testHelper) } /** @@ -597,9 +576,7 @@ class KeysBackupTest : InstrumentedTest { * - It must be trusted and must have with 2 signatures now */ @Test - fun trustKeyBackupVersionWithPasswordTest() { - val testHelper = CommonTestHelper(context()) - val cryptoTestHelper = CryptoTestHelper(testHelper) + fun trustKeyBackupVersionWithPasswordTest() = runCryptoTest(context()) { cryptoTestHelper, testHelper -> val keysBackupTestHelper = KeysBackupTestHelper(testHelper, cryptoTestHelper) val password = "Password" @@ -612,8 +589,8 @@ class KeysBackupTest : InstrumentedTest { // - The new device must see the previous backup as not trusted assertNotNull(testData.aliceSession2.cryptoService().keysBackupService().keysBackupVersion) - assertFalse(testData.aliceSession2.cryptoService().keysBackupService().isEnabled) - assertEquals(KeysBackupState.NotTrusted, testData.aliceSession2.cryptoService().keysBackupService().state) + assertFalse(testData.aliceSession2.cryptoService().keysBackupService().isEnabled()) + assertEquals(KeysBackupState.NotTrusted, testData.aliceSession2.cryptoService().keysBackupService().getState()) // - Trust the backup from the new device with the password testHelper.doSync { @@ -629,7 +606,7 @@ class KeysBackupTest : InstrumentedTest { // - Backup must be enabled on the new device, on the same version assertEquals(testData.prepareKeysBackupDataResult.version, testData.aliceSession2.cryptoService().keysBackupService().keysBackupVersion?.version) - assertTrue(testData.aliceSession2.cryptoService().keysBackupService().isEnabled) + assertTrue(testData.aliceSession2.cryptoService().keysBackupService().isEnabled()) // - Retrieve the last version from the server val keysVersionResult = testHelper.doSync { @@ -648,7 +625,6 @@ class KeysBackupTest : InstrumentedTest { assertEquals(2, keysBackupVersionTrust.signatures.size) stateObserver.stopAndCheckStates(null) - testData.cleanUp(testHelper) } /** @@ -660,9 +636,7 @@ class KeysBackupTest : InstrumentedTest { * - The backup must still be untrusted and disabled */ @Test - fun trustKeyBackupVersionWithWrongPasswordTest() { - val testHelper = CommonTestHelper(context()) - val cryptoTestHelper = CryptoTestHelper(testHelper) + fun trustKeyBackupVersionWithWrongPasswordTest() = runCryptoTest(context()) { cryptoTestHelper, testHelper -> val keysBackupTestHelper = KeysBackupTestHelper(testHelper, cryptoTestHelper) val password = "Password" @@ -676,8 +650,8 @@ class KeysBackupTest : InstrumentedTest { // - The new device must see the previous backup as not trusted assertNotNull(testData.aliceSession2.cryptoService().keysBackupService().keysBackupVersion) - assertFalse(testData.aliceSession2.cryptoService().keysBackupService().isEnabled) - assertEquals(KeysBackupState.NotTrusted, testData.aliceSession2.cryptoService().keysBackupService().state) + assertFalse(testData.aliceSession2.cryptoService().keysBackupService().isEnabled()) + assertEquals(KeysBackupState.NotTrusted, testData.aliceSession2.cryptoService().keysBackupService().getState()) // - Try to trust the backup from the new device with a wrong password val latch = CountDownLatch(1) @@ -690,11 +664,10 @@ class KeysBackupTest : InstrumentedTest { // - The new device must still see the previous backup as not trusted assertNotNull(testData.aliceSession2.cryptoService().keysBackupService().keysBackupVersion) - assertFalse(testData.aliceSession2.cryptoService().keysBackupService().isEnabled) - assertEquals(KeysBackupState.NotTrusted, testData.aliceSession2.cryptoService().keysBackupService().state) + assertFalse(testData.aliceSession2.cryptoService().keysBackupService().isEnabled()) + assertEquals(KeysBackupState.NotTrusted, testData.aliceSession2.cryptoService().keysBackupService().getState()) stateObserver.stopAndCheckStates(null) - testData.cleanUp(testHelper) } /** @@ -704,9 +677,7 @@ class KeysBackupTest : InstrumentedTest { * - It must fail */ @Test - fun restoreKeysBackupWithAWrongRecoveryKeyTest() { - val testHelper = CommonTestHelper(context()) - val cryptoTestHelper = CryptoTestHelper(testHelper) + fun restoreKeysBackupWithAWrongRecoveryKeyTest() = runCryptoTest(context()) { cryptoTestHelper, testHelper -> val keysBackupTestHelper = KeysBackupTestHelper(testHelper, cryptoTestHelper) val testData = keysBackupTestHelper.createKeysBackupScenarioWithPassword(null) @@ -730,8 +701,6 @@ class KeysBackupTest : InstrumentedTest { // onSuccess may not have been called assertNull(importRoomKeysResult) - - testData.cleanUp(testHelper) } /** @@ -741,9 +710,7 @@ class KeysBackupTest : InstrumentedTest { * - Restore must be successful */ @Test - fun testBackupWithPassword() { - val testHelper = CommonTestHelper(context()) - val cryptoTestHelper = CryptoTestHelper(testHelper) + fun testBackupWithPassword() = runCryptoTest(context()) { cryptoTestHelper, testHelper -> val keysBackupTestHelper = KeysBackupTestHelper(testHelper, cryptoTestHelper) val password = "password" @@ -790,8 +757,6 @@ class KeysBackupTest : InstrumentedTest { assertEquals(100, (steps[104] as StepProgressListener.Step.ImportingKey).progress) keysBackupTestHelper.checkRestoreSuccess(testData, importRoomKeysResult.totalNumberOfKeys, importRoomKeysResult.successfullyNumberOfImportedKeys) - - testData.cleanUp(testHelper) } /** @@ -801,9 +766,7 @@ class KeysBackupTest : InstrumentedTest { * - It must fail */ @Test - fun restoreKeysBackupWithAWrongPasswordTest() { - val testHelper = CommonTestHelper(context()) - val cryptoTestHelper = CryptoTestHelper(testHelper) + fun restoreKeysBackupWithAWrongPasswordTest() = runCryptoTest(context()) { cryptoTestHelper, testHelper -> val keysBackupTestHelper = KeysBackupTestHelper(testHelper, cryptoTestHelper) val password = "password" @@ -830,8 +793,6 @@ class KeysBackupTest : InstrumentedTest { // onSuccess may not have been called assertNull(importRoomKeysResult) - - testData.cleanUp(testHelper) } /** @@ -841,9 +802,7 @@ class KeysBackupTest : InstrumentedTest { * - Restore must be successful */ @Test - fun testUseRecoveryKeyToRestoreAPasswordBasedKeysBackup() { - val testHelper = CommonTestHelper(context()) - val cryptoTestHelper = CryptoTestHelper(testHelper) + fun testUseRecoveryKeyToRestoreAPasswordBasedKeysBackup() = runCryptoTest(context()) { cryptoTestHelper, testHelper -> val keysBackupTestHelper = KeysBackupTestHelper(testHelper, cryptoTestHelper) val password = "password" @@ -863,8 +822,6 @@ class KeysBackupTest : InstrumentedTest { } keysBackupTestHelper.checkRestoreSuccess(testData, importRoomKeysResult.totalNumberOfKeys, importRoomKeysResult.successfullyNumberOfImportedKeys) - - testData.cleanUp(testHelper) } /** @@ -874,9 +831,7 @@ class KeysBackupTest : InstrumentedTest { * - It must fail */ @Test - fun testUsePasswordToRestoreARecoveryKeyBasedKeysBackup() { - val testHelper = CommonTestHelper(context()) - val cryptoTestHelper = CryptoTestHelper(testHelper) + fun testUsePasswordToRestoreARecoveryKeyBasedKeysBackup() = runCryptoTest(context()) { cryptoTestHelper, testHelper -> val keysBackupTestHelper = KeysBackupTestHelper(testHelper, cryptoTestHelper) val testData = keysBackupTestHelper.createKeysBackupScenarioWithPassword(null) @@ -900,8 +855,6 @@ class KeysBackupTest : InstrumentedTest { // onSuccess may not have been called assertNull(importRoomKeysResult) - - testData.cleanUp(testHelper) } /** @@ -909,9 +862,7 @@ class KeysBackupTest : InstrumentedTest { * - Check the returned KeysVersionResult is trusted */ @Test - fun testIsKeysBackupTrusted() { - val testHelper = CommonTestHelper(context()) - val cryptoTestHelper = CryptoTestHelper(testHelper) + fun testIsKeysBackupTrusted() = runCryptoTest(context()) { cryptoTestHelper, testHelper -> val keysBackupTestHelper = KeysBackupTestHelper(testHelper, cryptoTestHelper) // - Create a backup version @@ -945,7 +896,6 @@ class KeysBackupTest : InstrumentedTest { assertEquals(signature.device!!.deviceId, cryptoTestData.firstSession.sessionParams.deviceId) stateObserver.stopAndCheckStates(null) - cryptoTestData.cleanUp(testHelper) } /** @@ -957,9 +907,7 @@ class KeysBackupTest : InstrumentedTest { * -> That must fail and her backup state must be WrongBackUpVersion */ @Test - fun testBackupWhenAnotherBackupWasCreated() { - val testHelper = CommonTestHelper(context()) - val cryptoTestHelper = CryptoTestHelper(testHelper) + fun testBackupWhenAnotherBackupWasCreated() = runCryptoTest(context()) { cryptoTestHelper, testHelper -> val keysBackupTestHelper = KeysBackupTestHelper(testHelper, cryptoTestHelper) // - Create a backup version @@ -969,7 +917,7 @@ class KeysBackupTest : InstrumentedTest { val stateObserver = StateObserver(keysBackup) - assertFalse(keysBackup.isEnabled) + assertFalse(keysBackup.isEnabled()) // Wait for keys backup to be finished val latch0 = CountDownLatch(1) @@ -993,7 +941,7 @@ class KeysBackupTest : InstrumentedTest { // - Make alice back up her keys to her homeserver keysBackupTestHelper.prepareAndCreateKeysBackupData(keysBackup) - assertTrue(keysBackup.isEnabled) + assertTrue(keysBackup.isEnabled()) testHelper.await(latch0) @@ -1012,11 +960,10 @@ class KeysBackupTest : InstrumentedTest { testHelper.await(latch2) // -> That must fail and her backup state must be WrongBackUpVersion - assertEquals(KeysBackupState.WrongBackUpVersion, keysBackup.state) - assertFalse(keysBackup.isEnabled) + assertEquals(KeysBackupState.WrongBackUpVersion, keysBackup.getState()) + assertFalse(keysBackup.isEnabled()) stateObserver.stopAndCheckStates(null) - cryptoTestData.cleanUp(testHelper) } /** @@ -1032,9 +979,7 @@ class KeysBackupTest : InstrumentedTest { * -> It must success */ @Test - fun testBackupAfterVerifyingADevice() { - val testHelper = CommonTestHelper(context()) - val cryptoTestHelper = CryptoTestHelper(testHelper) + fun testBackupAfterVerifyingADevice() = runCryptoTest(context()) { cryptoTestHelper, testHelper -> val keysBackupTestHelper = KeysBackupTestHelper(testHelper, cryptoTestHelper) // - Create a backup version @@ -1069,7 +1014,7 @@ class KeysBackupTest : InstrumentedTest { // - Try to backup all in aliceSession2, it must fail val keysBackup2 = aliceSession2.cryptoService().keysBackupService() - assertFalse("Backup should not be enabled", keysBackup2.isEnabled) + assertFalse("Backup should not be enabled", keysBackup2.isEnabled()) val stateObserver2 = StateObserver(keysBackup2) @@ -1088,8 +1033,8 @@ class KeysBackupTest : InstrumentedTest { assertFalse(isSuccessful) // Backup state must be NotTrusted - assertEquals("Backup state must be NotTrusted", KeysBackupState.NotTrusted, keysBackup2.state) - assertFalse("Backup should not be enabled", keysBackup2.isEnabled) + assertEquals("Backup state must be NotTrusted", KeysBackupState.NotTrusted, keysBackup2.getState()) + assertFalse("Backup should not be enabled", keysBackup2.isEnabled()) // - Validate the old device from the new one aliceSession2.cryptoService().setDeviceVerification( @@ -1103,7 +1048,7 @@ class KeysBackupTest : InstrumentedTest { keysBackup2.addListener(object : KeysBackupStateListener { override fun onStateChange(newState: KeysBackupState) { // Check the backup completes - if (keysBackup2.state == KeysBackupState.ReadyToBackUp) { + if (keysBackup2.getState() == KeysBackupState.ReadyToBackUp) { // Remove itself from the list of listeners keysBackup2.removeListener(this) @@ -1121,12 +1066,10 @@ class KeysBackupTest : InstrumentedTest { } // -> It must success - assertTrue(aliceSession2.cryptoService().keysBackupService().isEnabled) + assertTrue(aliceSession2.cryptoService().keysBackupService().isEnabled()) stateObserver.stopAndCheckStates(null) stateObserver2.stopAndCheckStates(null) - testHelper.signOutAndClose(aliceSession2) - cryptoTestData.cleanUp(testHelper) } /** @@ -1134,9 +1077,7 @@ class KeysBackupTest : InstrumentedTest { * - Delete the backup */ @Test - fun deleteKeysBackupTest() { - val testHelper = CommonTestHelper(context()) - val cryptoTestHelper = CryptoTestHelper(testHelper) + fun deleteKeysBackupTest() = runCryptoTest(context()) { cryptoTestHelper, testHelper -> val keysBackupTestHelper = KeysBackupTestHelper(testHelper, cryptoTestHelper) // - Create a backup version @@ -1146,19 +1087,18 @@ class KeysBackupTest : InstrumentedTest { val stateObserver = StateObserver(keysBackup) - assertFalse(keysBackup.isEnabled) + assertFalse(keysBackup.isEnabled()) val keyBackupCreationInfo = keysBackupTestHelper.prepareAndCreateKeysBackupData(keysBackup) - assertTrue(keysBackup.isEnabled) + assertTrue(keysBackup.isEnabled()) // Delete the backup testHelper.doSync { keysBackup.deleteBackup(keyBackupCreationInfo.version, it) } // Backup is now disabled - assertFalse(keysBackup.isEnabled) + assertFalse(keysBackup.isEnabled()) stateObserver.stopAndCheckStates(null) - cryptoTestData.cleanUp(testHelper) } } diff --git a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/keysbackup/KeysBackupTestHelper.kt b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/keysbackup/KeysBackupTestHelper.kt index 2220536e28..38f94c5103 100644 --- a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/keysbackup/KeysBackupTestHelper.kt +++ b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/keysbackup/KeysBackupTestHelper.kt @@ -33,7 +33,8 @@ import java.util.concurrent.CountDownLatch internal class KeysBackupTestHelper( private val testHelper: CommonTestHelper, - private val cryptoTestHelper: CryptoTestHelper) { + private val cryptoTestHelper: CryptoTestHelper +) { fun waitForKeybackUpBatching() { Thread.sleep(400) @@ -96,8 +97,10 @@ internal class KeysBackupTestHelper( ) } - fun prepareAndCreateKeysBackupData(keysBackup: KeysBackupService, - password: String? = null): PrepareKeysBackupDataResult { + fun prepareAndCreateKeysBackupData( + keysBackup: KeysBackupService, + password: String? = null + ): PrepareKeysBackupDataResult { val stateObserver = StateObserver(keysBackup) val megolmBackupCreationInfo = testHelper.doSync { @@ -106,7 +109,7 @@ internal class KeysBackupTestHelper( Assert.assertNotNull(megolmBackupCreationInfo) - Assert.assertFalse("Key backup should not be enabled before creation", keysBackup.isEnabled) + Assert.assertFalse("Key backup should not be enabled before creation", keysBackup.isEnabled()) // Create the version val keysVersion = testHelper.doSync { @@ -116,7 +119,7 @@ internal class KeysBackupTestHelper( Assert.assertNotNull("Key backup version should not be null", keysVersion.version) // Backup must be enable now - Assert.assertTrue(keysBackup.isEnabled) + Assert.assertTrue(keysBackup.isEnabled()) stateObserver.stopAndCheckStates(null) return PrepareKeysBackupDataResult(megolmBackupCreationInfo, keysVersion.version) @@ -128,7 +131,7 @@ internal class KeysBackupTestHelper( */ fun waitForKeysBackupToBeInState(session: Session, state: KeysBackupState) { // If already in the wanted state, return - if (session.cryptoService().keysBackupService().state == state) { + if (session.cryptoService().keysBackupService().getState() == state) { return } @@ -169,9 +172,11 @@ internal class KeysBackupTestHelper( * - The new device must have the same count of megolm keys * - Alice must have the same keys on both devices */ - fun checkRestoreSuccess(testData: KeysBackupScenarioData, - total: Int, - imported: Int) { + fun checkRestoreSuccess( + testData: KeysBackupScenarioData, + total: Int, + imported: Int + ) { // - Imported keys number must be correct Assert.assertEquals(testData.aliceKeys.size, total) Assert.assertEquals(total, imported) diff --git a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/keysbackup/PrepareKeysBackupDataResult.kt b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/keysbackup/PrepareKeysBackupDataResult.kt index 31bd3c9cce..9ee10eddcf 100644 --- a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/keysbackup/PrepareKeysBackupDataResult.kt +++ b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/keysbackup/PrepareKeysBackupDataResult.kt @@ -18,5 +18,7 @@ package org.matrix.android.sdk.internal.crypto.keysbackup import org.matrix.android.sdk.api.session.crypto.keysbackup.MegolmBackupCreationInfo -data class PrepareKeysBackupDataResult(val megolmBackupCreationInfo: MegolmBackupCreationInfo, - val version: String) +data class PrepareKeysBackupDataResult( + val megolmBackupCreationInfo: MegolmBackupCreationInfo, + val version: String +) diff --git a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/keysbackup/StateObserver.kt b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/keysbackup/StateObserver.kt index 80e54d82ec..6c97774547 100644 --- a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/keysbackup/StateObserver.kt +++ b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/keysbackup/StateObserver.kt @@ -27,9 +27,11 @@ import java.util.concurrent.CountDownLatch * This class observe the state change of a KeysBackup object and provide a method to check the several state change * It checks all state transitions and detected forbidden transition */ -internal class StateObserver(private val keysBackup: KeysBackupService, - private val latch: CountDownLatch? = null, - private val expectedStateChange: Int = -1) : KeysBackupStateListener { +internal class StateObserver( + private val keysBackup: KeysBackupService, + private val latch: CountDownLatch? = null, + private val expectedStateChange: Int = -1 +) : KeysBackupStateListener { private val allowedStateTransitions = listOf( KeysBackupState.BackingUp to KeysBackupState.ReadyToBackUp, diff --git a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/replayattack/ReplayAttackTest.kt b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/replayattack/ReplayAttackTest.kt new file mode 100644 index 0000000000..53cf802b91 --- /dev/null +++ b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/replayattack/ReplayAttackTest.kt @@ -0,0 +1,109 @@ +/* + * Copyright 2022 The Matrix.org Foundation C.I.C. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.matrix.android.sdk.internal.crypto.replayattack + +import androidx.test.filters.LargeTest +import org.amshove.kluent.internal.assertFailsWith +import org.junit.Assert +import org.junit.Assert.assertEquals +import org.junit.Assert.fail +import org.junit.FixMethodOrder +import org.junit.Test +import org.junit.runner.RunWith +import org.junit.runners.JUnit4 +import org.junit.runners.MethodSorters +import org.matrix.android.sdk.InstrumentedTest +import org.matrix.android.sdk.api.session.crypto.MXCryptoError +import org.matrix.android.sdk.common.CommonTestHelper.Companion.runCryptoTest + +@RunWith(JUnit4::class) +@FixMethodOrder(MethodSorters.JVM) +@LargeTest +class ReplayAttackTest : InstrumentedTest { + + @Test + fun replayAttackAlreadyDecryptedEventTest() = runCryptoTest(context()) { cryptoTestHelper, testHelper -> + val cryptoTestData = cryptoTestHelper.doE2ETestWithAliceAndBobInARoom(true) + + val e2eRoomID = cryptoTestData.roomId + + // Alice + val aliceSession = cryptoTestData.firstSession + val aliceRoomPOV = aliceSession.roomService().getRoom(e2eRoomID)!! + + // Bob + val bobSession = cryptoTestData.secondSession + val bobRoomPOV = bobSession!!.roomService().getRoom(e2eRoomID)!! + assertEquals(bobRoomPOV.roomSummary()?.joinedMembersCount, 2) + + // Alice will send a message + val sentEvents = testHelper.sendTextMessage(aliceRoomPOV, "Hello I will be decrypted twice", 1) + assertEquals(1, sentEvents.size) + + val fakeEventId = sentEvents[0].eventId + "_fake" + val fakeEventWithTheSameIndex = + sentEvents[0].copy(eventId = fakeEventId, root = sentEvents[0].root.copy(eventId = fakeEventId)) + + testHelper.runBlockingTest { + // Lets assume we are from the main timelineId + val timelineId = "timelineId" + // Lets decrypt the original event + aliceSession.cryptoService().decryptEvent(sentEvents[0].root, timelineId) + // Lets decrypt the fake event that will have the same message index + val exception = assertFailsWith { + // An exception should be thrown while the same index would have been used for the previous decryption + aliceSession.cryptoService().decryptEvent(fakeEventWithTheSameIndex.root, timelineId) + } + assertEquals(MXCryptoError.ErrorType.DUPLICATED_MESSAGE_INDEX, exception.errorType) + } + cryptoTestData.cleanUp(testHelper) + } + + @Test + fun replayAttackSameEventTest() = runCryptoTest(context()) { cryptoTestHelper, testHelper -> + val cryptoTestData = cryptoTestHelper.doE2ETestWithAliceAndBobInARoom(true) + + val e2eRoomID = cryptoTestData.roomId + + // Alice + val aliceSession = cryptoTestData.firstSession + val aliceRoomPOV = aliceSession.roomService().getRoom(e2eRoomID)!! + + // Bob + val bobSession = cryptoTestData.secondSession + val bobRoomPOV = bobSession!!.roomService().getRoom(e2eRoomID)!! + assertEquals(bobRoomPOV.roomSummary()?.joinedMembersCount, 2) + + // Alice will send a message + val sentEvents = testHelper.sendTextMessage(aliceRoomPOV, "Hello I will be decrypted twice", 1) + Assert.assertTrue("Message should be sent", sentEvents.size == 1) + assertEquals(sentEvents.size, 1) + + testHelper.runBlockingTest { + // Lets assume we are from the main timelineId + val timelineId = "timelineId" + // Lets decrypt the original event + aliceSession.cryptoService().decryptEvent(sentEvents[0].root, timelineId) + try { + // Lets try to decrypt the same event + aliceSession.cryptoService().decryptEvent(sentEvents[0].root, timelineId) + } catch (ex: Throwable) { + fail("Shouldn't throw a decryption error for same event") + } + } + } +} 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 c758050fc9..c8be6aae74 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 @@ -31,15 +31,16 @@ import org.matrix.android.sdk.api.crypto.SSSS_ALGORITHM_AES_HMAC_SHA2 import org.matrix.android.sdk.api.session.Session import org.matrix.android.sdk.api.session.accountdata.UserAccountDataEvent import org.matrix.android.sdk.api.session.securestorage.EncryptedSecretContent +import org.matrix.android.sdk.api.session.securestorage.KeyRef import org.matrix.android.sdk.api.session.securestorage.KeySigner import org.matrix.android.sdk.api.session.securestorage.RawBytesKeySpec import org.matrix.android.sdk.api.session.securestorage.SecretStorageKeyContent import org.matrix.android.sdk.api.session.securestorage.SharedSecretStorageError -import org.matrix.android.sdk.api.session.securestorage.SharedSecretStorageService import org.matrix.android.sdk.api.session.securestorage.SsssKeyCreationInfo import org.matrix.android.sdk.api.util.Optional import org.matrix.android.sdk.api.util.toBase64NoPadding import org.matrix.android.sdk.common.CommonTestHelper +import org.matrix.android.sdk.common.CommonTestHelper.Companion.runSessionTest import org.matrix.android.sdk.common.SessionTestParams import org.matrix.android.sdk.common.TestConstants import org.matrix.android.sdk.internal.crypto.secrets.DefaultSharedSecretStorageService @@ -55,8 +56,7 @@ class QuadSTests : InstrumentedTest { } @Test - fun test_Generate4SKey() { - val testHelper = CommonTestHelper(context()) + fun test_Generate4SKey() = runSessionTest(context()) { testHelper -> val aliceSession = testHelper.createAccount(TestConstants.USER_ALICE, SessionTestParams(true)) @@ -108,12 +108,11 @@ class QuadSTests : InstrumentedTest { } @Test - fun test_StoreSecret() { - val testHelper = CommonTestHelper(context()) + fun test_StoreSecret() = runSessionTest(context()) { testHelper -> val aliceSession = testHelper.createAccount(TestConstants.USER_ALICE, SessionTestParams(true)) val keyId = "My.Key" - val info = generatedSecret(aliceSession, keyId, true) + val info = generatedSecret(testHelper, aliceSession, keyId, true) val keySpec = RawBytesKeySpec.fromRecoveryKey(info.recoveryKey) @@ -123,11 +122,11 @@ class QuadSTests : InstrumentedTest { aliceSession.sharedSecretStorageService().storeSecret( "secret.of.life", clearSecret, - listOf(SharedSecretStorageService.KeyRef(null, keySpec)) // default key + listOf(KeyRef(null, keySpec)) // default key ) } - val secretAccountData = assertAccountData(aliceSession, "secret.of.life") + val secretAccountData = assertAccountData(testHelper, aliceSession, "secret.of.life") val encryptedContent = secretAccountData.content["encrypted"] as? Map<*, *> assertNotNull("Element should be encrypted", encryptedContent) @@ -149,12 +148,10 @@ class QuadSTests : InstrumentedTest { } assertEquals("Secret mismatch", clearSecret, decryptedSecret) - testHelper.signOutAndClose(aliceSession) } @Test - fun test_SetDefaultLocalEcho() { - val testHelper = CommonTestHelper(context()) + fun test_SetDefaultLocalEcho() = runSessionTest(context()) { testHelper -> val aliceSession = testHelper.createAccount(TestConstants.USER_ALICE, SessionTestParams(true)) @@ -170,19 +167,16 @@ class QuadSTests : InstrumentedTest { testHelper.runBlockingTest { quadS.setDefaultKey(TEST_KEY_ID) } - - testHelper.signOutAndClose(aliceSession) } @Test - fun test_StoreSecretWithMultipleKey() { - val testHelper = CommonTestHelper(context()) + fun test_StoreSecretWithMultipleKey() = runSessionTest(context()) { testHelper -> val aliceSession = testHelper.createAccount(TestConstants.USER_ALICE, SessionTestParams(true)) val keyId1 = "Key.1" - val key1Info = generatedSecret(aliceSession, keyId1, true) + val key1Info = generatedSecret(testHelper, aliceSession, keyId1, true) val keyId2 = "Key2" - val key2Info = generatedSecret(aliceSession, keyId2, true) + val key2Info = generatedSecret(testHelper, aliceSession, keyId2, true) val mySecretText = "Lorem ipsum dolor sit amet, consectetur adipiscing elit" @@ -191,8 +185,8 @@ class QuadSTests : InstrumentedTest { "my.secret", mySecretText.toByteArray().toBase64NoPadding(), listOf( - SharedSecretStorageService.KeyRef(keyId1, RawBytesKeySpec.fromRecoveryKey(key1Info.recoveryKey)), - SharedSecretStorageService.KeyRef(keyId2, RawBytesKeySpec.fromRecoveryKey(key2Info.recoveryKey)) + KeyRef(keyId1, RawBytesKeySpec.fromRecoveryKey(key1Info.recoveryKey)), + KeyRef(keyId2, RawBytesKeySpec.fromRecoveryKey(key2Info.recoveryKey)) ) ) } @@ -221,19 +215,16 @@ class QuadSTests : InstrumentedTest { RawBytesKeySpec.fromRecoveryKey(key2Info.recoveryKey)!! ) } - - testHelper.signOutAndClose(aliceSession) } @Test @Ignore("Test is working locally, not in GitHub actions") - fun test_GetSecretWithBadPassphrase() { - val testHelper = CommonTestHelper(context()) + fun test_GetSecretWithBadPassphrase() = runSessionTest(context()) { testHelper -> val aliceSession = testHelper.createAccount(TestConstants.USER_ALICE, SessionTestParams(true)) val keyId1 = "Key.1" val passphrase = "The good pass phrase" - val key1Info = generatedSecretFromPassphrase(aliceSession, passphrase, keyId1, true) + val key1Info = generatedSecretFromPassphrase(testHelper, aliceSession, passphrase, keyId1, true) val mySecretText = "Lorem ipsum dolor sit amet, consectetur adipiscing elit" @@ -241,7 +232,7 @@ class QuadSTests : InstrumentedTest { aliceSession.sharedSecretStorageService().storeSecret( "my.secret", mySecretText.toByteArray().toBase64NoPadding(), - listOf(SharedSecretStorageService.KeyRef(keyId1, RawBytesKeySpec.fromRecoveryKey(key1Info.recoveryKey))) + listOf(KeyRef(keyId1, RawBytesKeySpec.fromRecoveryKey(key1Info.recoveryKey))) ) } @@ -275,13 +266,9 @@ class QuadSTests : InstrumentedTest { ) ) } - - testHelper.signOutAndClose(aliceSession) } - private fun assertAccountData(session: Session, type: String): UserAccountDataEvent { - val testHelper = CommonTestHelper(context()) - + private fun assertAccountData(testHelper: CommonTestHelper, session: Session, type: String): UserAccountDataEvent { var accountData: UserAccountDataEvent? = null testHelper.waitWithLatch { val liveAccountData = session.accountDataService().getLiveUserAccountDataEvent(type) @@ -297,29 +284,27 @@ class QuadSTests : InstrumentedTest { return accountData!! } - private fun generatedSecret(session: Session, keyId: String, asDefault: Boolean = true): SsssKeyCreationInfo { + private fun generatedSecret(testHelper: CommonTestHelper, session: Session, keyId: String, asDefault: Boolean = true): SsssKeyCreationInfo { val quadS = session.sharedSecretStorageService() - val testHelper = CommonTestHelper(context()) val creationInfo = testHelper.runBlockingTest { quadS.generateKey(keyId, null, keyId, emptyKeySigner) } - assertAccountData(session, "${DefaultSharedSecretStorageService.KEY_ID_BASE}.$keyId") + assertAccountData(testHelper, session, "${DefaultSharedSecretStorageService.KEY_ID_BASE}.$keyId") if (asDefault) { testHelper.runBlockingTest { quadS.setDefaultKey(keyId) } - assertAccountData(session, DefaultSharedSecretStorageService.DEFAULT_KEY_ID) + assertAccountData(testHelper, session, DefaultSharedSecretStorageService.DEFAULT_KEY_ID) } return creationInfo } - private fun generatedSecretFromPassphrase(session: Session, passphrase: String, keyId: String, asDefault: Boolean = true): SsssKeyCreationInfo { + private fun generatedSecretFromPassphrase(testHelper: CommonTestHelper, session: Session, passphrase: String, keyId: String, asDefault: Boolean = true): SsssKeyCreationInfo { val quadS = session.sharedSecretStorageService() - val testHelper = CommonTestHelper(context()) val creationInfo = testHelper.runBlockingTest { quadS.generateKeyWithPassphrase( @@ -331,12 +316,12 @@ class QuadSTests : InstrumentedTest { ) } - assertAccountData(session, "${DefaultSharedSecretStorageService.KEY_ID_BASE}.$keyId") + assertAccountData(testHelper, session, "${DefaultSharedSecretStorageService.KEY_ID_BASE}.$keyId") if (asDefault) { testHelper.runBlockingTest { quadS.setDefaultKey(keyId) } - assertAccountData(session, DefaultSharedSecretStorageService.DEFAULT_KEY_ID) + assertAccountData(testHelper, session, DefaultSharedSecretStorageService.DEFAULT_KEY_ID) } return creationInfo diff --git a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/verification/SASTest.kt b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/verification/SASTest.kt index 2892cf8464..c2e74abc59 100644 --- a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/verification/SASTest.kt +++ b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/verification/SASTest.kt @@ -44,8 +44,7 @@ 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.Event import org.matrix.android.sdk.api.session.events.model.toModel -import org.matrix.android.sdk.common.CommonTestHelper -import org.matrix.android.sdk.common.CryptoTestHelper +import org.matrix.android.sdk.common.CommonTestHelper.Companion.runCryptoTest import org.matrix.android.sdk.internal.crypto.model.rest.KeyVerificationCancel import org.matrix.android.sdk.internal.crypto.model.rest.KeyVerificationStart import org.matrix.android.sdk.internal.crypto.model.rest.toValue @@ -53,12 +52,11 @@ import java.util.concurrent.CountDownLatch @RunWith(AndroidJUnit4::class) @FixMethodOrder(MethodSorters.NAME_ASCENDING) +@Ignore class SASTest : InstrumentedTest { @Test - fun test_aliceStartThenAliceCancel() { - val testHelper = CommonTestHelper(context()) - val cryptoTestHelper = CryptoTestHelper(testHelper) + fun test_aliceStartThenAliceCancel() = runCryptoTest(context()) { cryptoTestHelper, testHelper -> val cryptoTestData = cryptoTestHelper.doE2ETestWithAliceAndBobInARoom() val aliceSession = cryptoTestData.firstSession @@ -135,15 +133,11 @@ class SASTest : InstrumentedTest { assertNull(bobVerificationService.getExistingTransaction(aliceSession.myUserId, txID)) assertNull(aliceVerificationService.getExistingTransaction(bobSession.myUserId, txID)) - - cryptoTestData.cleanUp(testHelper) } @Test @Ignore("This test will be ignored until it is fixed") - fun test_key_agreement_protocols_must_include_curve25519() { - val testHelper = CommonTestHelper(context()) - val cryptoTestHelper = CryptoTestHelper(testHelper) + fun test_key_agreement_protocols_must_include_curve25519() = runCryptoTest(context()) { cryptoTestHelper, testHelper -> fail("Not passing for the moment") val cryptoTestData = cryptoTestHelper.doE2ETestWithAliceAndBobInARoom() @@ -195,15 +189,11 @@ class SASTest : InstrumentedTest { testHelper.await(cancelLatch) assertEquals("Request should be cancelled with m.unknown_method", CancelCode.UnknownMethod, cancelReason) - - cryptoTestData.cleanUp(testHelper) } @Test @Ignore("This test will be ignored until it is fixed") - fun test_key_agreement_macs_Must_include_hmac_sha256() { - val testHelper = CommonTestHelper(context()) - val cryptoTestHelper = CryptoTestHelper(testHelper) + fun test_key_agreement_macs_Must_include_hmac_sha256() = runCryptoTest(context()) { cryptoTestHelper, testHelper -> fail("Not passing for the moment") val cryptoTestData = cryptoTestHelper.doE2ETestWithAliceAndBobInARoom() @@ -236,15 +226,11 @@ class SASTest : InstrumentedTest { val cancelReq = canceledToDeviceEvent!!.content.toModel()!! assertEquals("Request should be cancelled with m.unknown_method", CancelCode.UnknownMethod.value, cancelReq.code) - - cryptoTestData.cleanUp(testHelper) } @Test @Ignore("This test will be ignored until it is fixed") - fun test_key_agreement_short_code_include_decimal() { - val testHelper = CommonTestHelper(context()) - val cryptoTestHelper = CryptoTestHelper(testHelper) + fun test_key_agreement_short_code_include_decimal() = runCryptoTest(context()) { cryptoTestHelper, testHelper -> fail("Not passing for the moment") val cryptoTestData = cryptoTestHelper.doE2ETestWithAliceAndBobInARoom() @@ -277,18 +263,18 @@ class SASTest : InstrumentedTest { val cancelReq = canceledToDeviceEvent!!.content.toModel()!! assertEquals("Request should be cancelled with m.unknown_method", CancelCode.UnknownMethod.value, cancelReq.code) - - cryptoTestData.cleanUp(testHelper) } - private fun fakeBobStart(bobSession: Session, - aliceUserID: String?, - aliceDevice: String?, - tid: String, - protocols: List = SASDefaultVerificationTransaction.KNOWN_AGREEMENT_PROTOCOLS, - hashes: List = SASDefaultVerificationTransaction.KNOWN_HASHES, - mac: List = SASDefaultVerificationTransaction.KNOWN_MACS, - codes: List = SASDefaultVerificationTransaction.KNOWN_SHORT_CODES) { + private fun fakeBobStart( + bobSession: Session, + aliceUserID: String?, + aliceDevice: String?, + tid: String, + protocols: List = SASDefaultVerificationTransaction.KNOWN_AGREEMENT_PROTOCOLS, + hashes: List = SASDefaultVerificationTransaction.KNOWN_HASHES, + mac: List = SASDefaultVerificationTransaction.KNOWN_MACS, + codes: List = SASDefaultVerificationTransaction.KNOWN_SHORT_CODES + ) { val startMessage = KeyVerificationStart( fromDevice = bobSession.cryptoService().getMyDevice().deviceId, method = VerificationMethod.SAS.toValue(), @@ -314,9 +300,7 @@ class SASTest : InstrumentedTest { // any two devices may only have at most one key verification in flight at a time. // If a device has two verifications in progress with the same device, then it should cancel both verifications. @Test - fun test_aliceStartTwoRequests() { - val testHelper = CommonTestHelper(context()) - val cryptoTestHelper = CryptoTestHelper(testHelper) + fun test_aliceStartTwoRequests() = runCryptoTest(context()) { cryptoTestHelper, testHelper -> val cryptoTestData = cryptoTestHelper.doE2ETestWithAliceAndBobInARoom() val aliceSession = cryptoTestData.firstSession @@ -357,9 +341,7 @@ class SASTest : InstrumentedTest { */ @Test @Ignore("This test will be ignored until it is fixed") - fun test_aliceAndBobAgreement() { - val testHelper = CommonTestHelper(context()) - val cryptoTestHelper = CryptoTestHelper(testHelper) + fun test_aliceAndBobAgreement() = runCryptoTest(context()) { cryptoTestHelper, testHelper -> val cryptoTestData = cryptoTestHelper.doE2ETestWithAliceAndBobInARoom() val aliceSession = cryptoTestData.firstSession @@ -413,14 +395,10 @@ class SASTest : InstrumentedTest { accepted!!.shortAuthenticationStrings.forEach { assertTrue("all agreed Short Code should be known by alice", startReq!!.shortAuthenticationStrings.contains(it)) } - - cryptoTestData.cleanUp(testHelper) } @Test - fun test_aliceAndBobSASCode() { - val testHelper = CommonTestHelper(context()) - val cryptoTestHelper = CryptoTestHelper(testHelper) + fun test_aliceAndBobSASCode() = runCryptoTest(context()) { cryptoTestHelper, testHelper -> val cryptoTestData = cryptoTestHelper.doE2ETestWithAliceAndBobInARoom() val aliceSession = cryptoTestData.firstSession @@ -437,7 +415,7 @@ class SASTest : InstrumentedTest { OutgoingSasVerificationTransaction.UxState.SHOW_SAS -> { aliceSASLatch.countDown() } - else -> Unit + else -> Unit } } } @@ -451,7 +429,7 @@ class SASTest : InstrumentedTest { IncomingSasVerificationTransaction.UxState.SHOW_ACCEPT -> { tx.performAccept() } - else -> Unit + else -> Unit } if (uxState === IncomingSasVerificationTransaction.UxState.SHOW_SAS) { bobSASLatch.countDown() @@ -473,14 +451,10 @@ class SASTest : InstrumentedTest { "Should have same SAS", aliceTx.getShortCodeRepresentation(SasMode.DECIMAL), bobTx.getShortCodeRepresentation(SasMode.DECIMAL) ) - - cryptoTestData.cleanUp(testHelper) } @Test - fun test_happyPath() { - val testHelper = CommonTestHelper(context()) - val cryptoTestHelper = CryptoTestHelper(testHelper) + fun test_happyPath() = runCryptoTest(context()) { cryptoTestHelper, testHelper -> val cryptoTestData = cryptoTestHelper.doE2ETestWithAliceAndBobInARoom() val aliceSession = cryptoTestData.firstSession @@ -505,7 +479,7 @@ class SASTest : InstrumentedTest { aliceSASLatch.countDown() } } - else -> Unit + else -> Unit } } } @@ -525,16 +499,16 @@ class SASTest : InstrumentedTest { tx.performAccept() } } - IncomingSasVerificationTransaction.UxState.SHOW_SAS -> { + IncomingSasVerificationTransaction.UxState.SHOW_SAS -> { if (matchOnce) { matchOnce = false tx.userHasVerifiedShortCode() } } - IncomingSasVerificationTransaction.UxState.VERIFIED -> { + IncomingSasVerificationTransaction.UxState.VERIFIED -> { bobSASLatch.countDown() } - else -> Unit + else -> Unit } } } @@ -553,13 +527,10 @@ class SASTest : InstrumentedTest { assertTrue("alice device should be verified from bob point of view", aliceDeviceInfoFromBobPOV!!.isVerified) assertTrue("bob device should be verified from alice point of view", bobDeviceInfoFromAlicePOV!!.isVerified) - cryptoTestData.cleanUp(testHelper) } @Test - fun test_ConcurrentStart() { - val testHelper = CommonTestHelper(context()) - val cryptoTestHelper = CryptoTestHelper(testHelper) + fun test_ConcurrentStart() = runCryptoTest(context()) { cryptoTestHelper, testHelper -> val cryptoTestData = cryptoTestHelper.doE2ETestWithAliceAndBobInARoom() val aliceSession = cryptoTestData.firstSession @@ -646,7 +617,5 @@ class SASTest : InstrumentedTest { bobPovTx?.state == VerificationTxState.ShortCodeReady } } - - cryptoTestData.cleanUp(testHelper) } } 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 ceebc3cd01..3f22906965 100644 --- a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/verification/qrcode/VerificationTest.kt +++ b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/verification/qrcode/VerificationTest.kt @@ -19,6 +19,7 @@ package org.matrix.android.sdk.internal.crypto.verification.qrcode import androidx.test.ext.junit.runners.AndroidJUnit4 import org.amshove.kluent.shouldBe import org.junit.FixMethodOrder +import org.junit.Ignore import org.junit.Test import org.junit.runner.RunWith import org.junit.runners.MethodSorters @@ -31,8 +32,8 @@ import org.matrix.android.sdk.api.session.crypto.verification.CancelCode import org.matrix.android.sdk.api.session.crypto.verification.PendingVerificationRequest import org.matrix.android.sdk.api.session.crypto.verification.VerificationMethod import org.matrix.android.sdk.api.session.crypto.verification.VerificationService -import org.matrix.android.sdk.common.CommonTestHelper -import org.matrix.android.sdk.common.CryptoTestHelper +import org.matrix.android.sdk.common.CommonTestHelper.Companion.runCryptoTest +import org.matrix.android.sdk.common.CommonTestHelper.Companion.runSessionTest import org.matrix.android.sdk.common.SessionTestParams import org.matrix.android.sdk.common.TestConstants import java.util.concurrent.CountDownLatch @@ -41,6 +42,7 @@ import kotlin.coroutines.resume @RunWith(AndroidJUnit4::class) @FixMethodOrder(MethodSorters.JVM) +@Ignore class VerificationTest : InstrumentedTest { data class ExpectedResult( @@ -151,12 +153,12 @@ class VerificationTest : InstrumentedTest { // TODO Add tests without SAS - private fun doTest(aliceSupportedMethods: List, - bobSupportedMethods: List, - expectedResultForAlice: ExpectedResult, - expectedResultForBob: ExpectedResult) { - val testHelper = CommonTestHelper(context()) - val cryptoTestHelper = CryptoTestHelper(testHelper) + private fun doTest( + aliceSupportedMethods: List, + bobSupportedMethods: List, + expectedResultForAlice: ExpectedResult, + expectedResultForBob: ExpectedResult + ) = runCryptoTest(context()) { cryptoTestHelper, testHelper -> val cryptoTestData = cryptoTestHelper.doE2ETestWithAliceAndBobInARoom() val aliceSession = cryptoTestData.firstSession @@ -251,14 +253,11 @@ class VerificationTest : InstrumentedTest { pr.otherCanShowQrCode() shouldBe expectedResultForBob.otherCanShowQrCode pr.otherCanScanQrCode() shouldBe expectedResultForBob.otherCanScanQrCode } - - cryptoTestData.cleanUp(testHelper) } @Test - fun test_selfVerificationAcceptedCancelsItForOtherSessions() { + fun test_selfVerificationAcceptedCancelsItForOtherSessions() = runSessionTest(context()) { testHelper -> val defaultSessionParams = SessionTestParams(true) - val testHelper = CommonTestHelper(context()) val aliceSessionToVerify = testHelper.createAccount(TestConstants.USER_ALICE, defaultSessionParams) val aliceSessionThatVerifies = testHelper.logIntoAccount(aliceSessionToVerify.myUserId, TestConstants.PASSWORD, defaultSessionParams) diff --git a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/session/room/send/MarkdownParserTest.kt b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/session/room/send/MarkdownParserTest.kt index acb23bf723..0560cfec95 100644 --- a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/session/room/send/MarkdownParserTest.kt +++ b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/session/room/send/MarkdownParserTest.kt @@ -289,9 +289,11 @@ class MarkdownParserTest : InstrumentedTest { markdownParser.parse(text).expect(text, null) } - private fun testType(name: String, - markdownPattern: String, - htmlExpectedTag: String) { + private fun testType( + name: String, + markdownPattern: String, + htmlExpectedTag: String + ) { // Test simple case "$markdownPattern$name$markdownPattern" .let { @@ -376,10 +378,12 @@ class MarkdownParserTest : InstrumentedTest { } } - private fun testTypeNewLines(name: String, - markdownPattern: String, - htmlExpectedTag: String, - softBreak: String = "
") { + private fun testTypeNewLines( + name: String, + markdownPattern: String, + htmlExpectedTag: String, + softBreak: String = "
" + ) { // With new line inside the block "$markdownPattern$name\n$name$markdownPattern" .let { diff --git a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/session/room/send/TestPermalinkService.kt b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/session/room/send/TestPermalinkService.kt index 2f9a5e0a73..3a267ec694 100644 --- a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/session/room/send/TestPermalinkService.kt +++ b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/session/room/send/TestPermalinkService.kt @@ -44,7 +44,7 @@ class TestPermalinkService : PermalinkService { override fun createMentionSpanTemplate(type: PermalinkService.SpanTemplateType, forceMatrixTo: Boolean): String { return when (type) { - HTML -> "%2\$s" + HTML -> "%2\$s" MARKDOWN -> "[%2\$s](https://matrix.to/#/%1\$s)" } } 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 a2984dd27e..45bd38870d 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 @@ -34,8 +34,7 @@ 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 -import org.matrix.android.sdk.common.CryptoTestHelper +import org.matrix.android.sdk.common.CommonTestHelper.Companion.runCryptoTest import java.util.concurrent.CountDownLatch @RunWith(JUnit4::class) @@ -44,9 +43,7 @@ import java.util.concurrent.CountDownLatch class ThreadMessagingTest : InstrumentedTest { @Test - fun reply_in_thread_should_create_a_thread() { - val commonTestHelper = CommonTestHelper(context()) - val cryptoTestHelper = CryptoTestHelper(commonTestHelper) + fun reply_in_thread_should_create_a_thread() = runCryptoTest(context()) { cryptoTestHelper, commonTestHelper -> val cryptoTestData = cryptoTestHelper.doE2ETestWithAliceInARoom(false) val aliceSession = cryptoTestData.firstSession @@ -86,7 +83,7 @@ class ThreadMessagingTest : InstrumentedTest { val timeline = aliceRoom.timelineService().createTimeline(null, TimelineSettings(30)) timeline.start() - aliceSession.startSync(true) + aliceSession.syncService().startSync(true) run { val lock = CountDownLatch(1) val eventsListener = commonTestHelper.createEventListener(lock) { snapshot -> @@ -100,13 +97,11 @@ class ThreadMessagingTest : InstrumentedTest { timeline.addListener(eventsListener) commonTestHelper.await(lock, 600_000) } - aliceSession.stopSync() + aliceSession.syncService().stopSync() } @Test - fun reply_in_thread_should_create_a_thread_from_other_user() { - val commonTestHelper = CommonTestHelper(context()) - val cryptoTestHelper = CryptoTestHelper(commonTestHelper) + fun reply_in_thread_should_create_a_thread_from_other_user() = runCryptoTest(context()) { cryptoTestHelper, commonTestHelper -> val cryptoTestData = cryptoTestHelper.doE2ETestWithAliceAndBobInARoom(false) val aliceSession = cryptoTestData.firstSession @@ -149,7 +144,7 @@ class ThreadMessagingTest : InstrumentedTest { val timeline = aliceRoom.timelineService().createTimeline(null, TimelineSettings(30)) timeline.start() - aliceSession.startSync(true) + aliceSession.syncService().startSync(true) run { val lock = CountDownLatch(1) val eventsListener = commonTestHelper.createEventListener(lock) { snapshot -> @@ -161,9 +156,9 @@ class ThreadMessagingTest : InstrumentedTest { timeline.addListener(eventsListener) commonTestHelper.await(lock, 600_000) } - aliceSession.stopSync() + aliceSession.syncService().stopSync() - bobSession.startSync(true) + bobSession.syncService().startSync(true) run { val lock = CountDownLatch(1) val eventsListener = commonTestHelper.createEventListener(lock) { snapshot -> @@ -175,13 +170,11 @@ class ThreadMessagingTest : InstrumentedTest { timeline.addListener(eventsListener) commonTestHelper.await(lock, 600_000) } - bobSession.stopSync() + bobSession.syncService().stopSync() } @Test - fun reply_in_thread_to_timeline_message_multiple_times() { - val commonTestHelper = CommonTestHelper(context()) - val cryptoTestHelper = CryptoTestHelper(commonTestHelper) + fun reply_in_thread_to_timeline_message_multiple_times() = runCryptoTest(context()) { cryptoTestHelper, commonTestHelper -> val cryptoTestData = cryptoTestHelper.doE2ETestWithAliceInARoom(false) val aliceSession = cryptoTestData.firstSession @@ -224,7 +217,7 @@ class ThreadMessagingTest : InstrumentedTest { val timeline = aliceRoom.timelineService().createTimeline(null, TimelineSettings(30)) timeline.start() - aliceSession.startSync(true) + aliceSession.syncService().startSync(true) run { val lock = CountDownLatch(1) val eventsListener = commonTestHelper.createEventListener(lock) { snapshot -> @@ -240,13 +233,11 @@ class ThreadMessagingTest : InstrumentedTest { timeline.addListener(eventsListener) commonTestHelper.await(lock, 600_000) } - aliceSession.stopSync() + aliceSession.syncService().stopSync() } @Test - fun thread_summary_advanced_validation_after_multiple_messages_in_multiple_threads() { - val commonTestHelper = CommonTestHelper(context()) - val cryptoTestHelper = CryptoTestHelper(commonTestHelper) + fun thread_summary_advanced_validation_after_multiple_messages_in_multiple_threads() = runCryptoTest(context()) { cryptoTestHelper, commonTestHelper -> val cryptoTestData = cryptoTestHelper.doE2ETestWithAliceAndBobInARoom(false) val aliceSession = cryptoTestData.firstSession @@ -323,7 +314,7 @@ class ThreadMessagingTest : InstrumentedTest { val timeline = aliceRoom.timelineService().createTimeline(null, TimelineSettings(30)) timeline.start() - aliceSession.startSync(true) + aliceSession.syncService().startSync(true) run { val lock = CountDownLatch(1) val eventsListener = commonTestHelper.createEventListener(lock) { snapshot -> @@ -347,6 +338,6 @@ class ThreadMessagingTest : InstrumentedTest { timeline.addListener(eventsListener) commonTestHelper.await(lock, 600_000) } - aliceSession.stopSync() + aliceSession.syncService().stopSync() } } 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 94b2ba55a3..7b0d57abbc 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 @@ -22,7 +22,6 @@ import io.realm.Realm import io.realm.RealmConfiguration import io.realm.kotlin.createObject import org.amshove.kluent.shouldBeEqualTo -import org.amshove.kluent.shouldBeTrue import org.junit.Before import org.junit.Test import org.junit.runner.RunWith @@ -30,13 +29,11 @@ import org.matrix.android.sdk.InstrumentedTest import org.matrix.android.sdk.api.session.events.model.Event import org.matrix.android.sdk.api.session.room.send.SendState import org.matrix.android.sdk.internal.database.helper.addTimelineEvent -import org.matrix.android.sdk.internal.database.helper.merge 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 @RunWith(AndroidJUnit4::class) @@ -97,66 +94,11 @@ internal class ChunkEntityTest : InstrumentedTest { } } - @Test - fun merge_shouldAddEvents_whenMergingBackward() { - monarchy.runTransactionSync { realm -> - val chunk1: ChunkEntity = realm.createObject() - val chunk2: ChunkEntity = realm.createObject() - chunk1.addAll(ROOM_ID, createFakeListOfEvents(30), PaginationDirection.BACKWARDS) - chunk2.addAll(ROOM_ID, createFakeListOfEvents(30), PaginationDirection.BACKWARDS) - chunk1.merge(ROOM_ID, chunk2, PaginationDirection.BACKWARDS) - chunk1.timelineEvents.size shouldBeEqualTo 60 - } - } - - @Test - fun merge_shouldAddOnlyDifferentEvents_whenMergingBackward() { - monarchy.runTransactionSync { realm -> - val chunk1: ChunkEntity = realm.createObject() - val chunk2: ChunkEntity = realm.createObject() - val eventsForChunk1 = createFakeListOfEvents(30) - val eventsForChunk2 = eventsForChunk1 + createFakeListOfEvents(10) - chunk1.isLastForward = true - chunk2.isLastForward = false - chunk1.addAll(ROOM_ID, eventsForChunk1, PaginationDirection.FORWARDS) - chunk2.addAll(ROOM_ID, eventsForChunk2, PaginationDirection.BACKWARDS) - chunk1.merge(ROOM_ID, chunk2, PaginationDirection.BACKWARDS) - chunk1.timelineEvents.size shouldBeEqualTo 40 - chunk1.isLastForward.shouldBeTrue() - } - } - - @Test - fun merge_shouldPrevTokenMerged_whenMergingForwards() { - monarchy.runTransactionSync { realm -> - val chunk1: ChunkEntity = realm.createObject() - val chunk2: ChunkEntity = realm.createObject() - val prevToken = "prev_token" - chunk1.prevToken = prevToken - chunk1.addAll(ROOM_ID, createFakeListOfEvents(30), PaginationDirection.BACKWARDS) - chunk2.addAll(ROOM_ID, createFakeListOfEvents(30), PaginationDirection.BACKWARDS) - chunk1.merge(ROOM_ID, chunk2, PaginationDirection.FORWARDS) - chunk1.prevToken shouldBeEqualTo prevToken - } - } - - @Test - fun merge_shouldNextTokenMerged_whenMergingBackwards() { - monarchy.runTransactionSync { realm -> - val chunk1: ChunkEntity = realm.createObject() - val chunk2: ChunkEntity = realm.createObject() - val nextToken = "next_token" - chunk1.nextToken = nextToken - chunk1.addAll(ROOM_ID, createFakeListOfEvents(30), PaginationDirection.BACKWARDS) - chunk2.addAll(ROOM_ID, createFakeListOfEvents(30), PaginationDirection.BACKWARDS) - chunk1.merge(ROOM_ID, chunk2, PaginationDirection.BACKWARDS) - chunk1.nextToken shouldBeEqualTo nextToken - } - } - - private fun ChunkEntity.addAll(roomId: String, - events: List, - direction: PaginationDirection) { + private fun ChunkEntity.addAll( + roomId: String, + events: List, + direction: PaginationDirection + ) { events.forEach { event -> val fakeEvent = event.toEntity(roomId, SendState.SYNCED, clock.epochMillis()).let { realm.copyToRealm(it) diff --git a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/session/room/timeline/FakeTokenChunkEvent.kt b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/session/room/timeline/FakeTokenChunkEvent.kt index 657f622c5b..2e9478ba7e 100644 --- a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/session/room/timeline/FakeTokenChunkEvent.kt +++ b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/session/room/timeline/FakeTokenChunkEvent.kt @@ -19,8 +19,9 @@ package org.matrix.android.sdk.session.room.timeline import org.matrix.android.sdk.api.session.events.model.Event import org.matrix.android.sdk.internal.session.room.timeline.TokenChunkEvent -internal data class FakeTokenChunkEvent(override val start: String?, - override val end: String?, - override val events: List = emptyList(), - override val stateEvents: List = emptyList() +internal data class FakeTokenChunkEvent( + override val start: String?, + override val end: String?, + override val events: List = emptyList(), + override val stateEvents: List = emptyList() ) : TokenChunkEvent 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 61ab6d4b40..a37d2ce015 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 @@ -38,8 +38,7 @@ import org.matrix.android.sdk.api.session.room.model.message.PollType 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 -import org.matrix.android.sdk.common.CommonTestHelper -import org.matrix.android.sdk.common.CryptoTestHelper +import org.matrix.android.sdk.common.CommonTestHelper.Companion.runCryptoTest import java.util.concurrent.CountDownLatch @RunWith(JUnit4::class) @@ -47,9 +46,7 @@ import java.util.concurrent.CountDownLatch class PollAggregationTest : InstrumentedTest { @Test - fun testAllPollUseCases() { - val commonTestHelper = CommonTestHelper(context()) - val cryptoTestHelper = CryptoTestHelper(commonTestHelper) + fun testAllPollUseCases() = runCryptoTest(context()) { cryptoTestHelper, commonTestHelper -> val cryptoTestData = cryptoTestHelper.doE2ETestWithAliceAndBobInARoom(false) val aliceSession = cryptoTestData.firstSession @@ -60,7 +57,7 @@ class PollAggregationTest : InstrumentedTest { // Bob creates a poll roomFromBobPOV.sendService().sendPoll(PollType.DISCLOSED, pollQuestion, pollOptions) - aliceSession.startSync(true) + aliceSession.syncService().startSync(true) val aliceTimeline = roomFromAlicePOV.timelineService().createTimeline(null, TimelineSettings(30)) aliceTimeline.start() @@ -80,7 +77,7 @@ class PollAggregationTest : InstrumentedTest { } when (lock.count.toInt()) { - TOTAL_TEST_COUNT -> { + TOTAL_TEST_COUNT -> { // Poll has just been created. testInitialPollConditions(pollContent, pollSummary) lock.countDown() @@ -122,7 +119,7 @@ class PollAggregationTest : InstrumentedTest { testEndedPoll(pollSummary) lock.countDown() } - else -> { + else -> { fail("Lock count ${lock.count} didn't handled.") } } @@ -136,9 +133,8 @@ class PollAggregationTest : InstrumentedTest { aliceTimeline.removeAllListeners() - aliceSession.stopSync() + aliceSession.syncService().stopSync() aliceTimeline.dispose() - cryptoTestData.cleanUp(commonTestHelper) } private fun testInitialPollConditions(pollContent: MessagePollContent, pollSummary: PollResponseAggregatedSummary?) { diff --git a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/session/room/timeline/RoomDataHelper.kt b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/session/room/timeline/RoomDataHelper.kt index 8a4429db45..53585ae82a 100644 --- a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/session/room/timeline/RoomDataHelper.kt +++ b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/session/room/timeline/RoomDataHelper.kt @@ -41,11 +41,12 @@ object RoomDataHelper { } } - private fun createFakeEvent(type: String, - content: Content? = null, - prevContent: Content? = null, - sender: String = FAKE_TEST_SENDER, - stateKey: String? = null + private fun createFakeEvent( + type: String, + content: Content? = null, + prevContent: Content? = null, + sender: String = FAKE_TEST_SENDER, + stateKey: String? = null ): Event { return Event( type = type, 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 e407c1b42d..3dbf206e08 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 @@ -34,8 +34,7 @@ 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 -import org.matrix.android.sdk.common.CommonTestHelper -import org.matrix.android.sdk.common.CryptoTestHelper +import org.matrix.android.sdk.common.CommonTestHelper.Companion.runCryptoTest import org.matrix.android.sdk.common.checkSendOrder import timber.log.Timber import java.util.concurrent.CountDownLatch @@ -53,9 +52,7 @@ class TimelineForwardPaginationTest : InstrumentedTest { * This test ensure that if we click to permalink, we will be able to go back to the live */ @Test - fun forwardPaginationTest() { - val commonTestHelper = CommonTestHelper(context()) - val cryptoTestHelper = CryptoTestHelper(commonTestHelper) + fun forwardPaginationTest() = runCryptoTest(context()) { cryptoTestHelper, commonTestHelper -> val numberOfMessagesToSend = 90 val cryptoTestData = cryptoTestHelper.doE2ETestWithAliceInARoom(false) @@ -166,6 +163,8 @@ class TimelineForwardPaginationTest : InstrumentedTest { // Ask for a forward pagination val snapshot = runBlocking { aliceTimeline.awaitPaginate(Timeline.Direction.FORWARDS, 50) + // We should paginate one more time to check we are at the end now that chunks are not merged. + aliceTimeline.awaitPaginate(Timeline.Direction.FORWARDS, 50) } // 7 for room creation item (backward pagination),and numberOfMessagesToSend (all the message of the room) snapshot.size == 7 + numberOfMessagesToSend && @@ -177,7 +176,5 @@ class TimelineForwardPaginationTest : InstrumentedTest { } aliceTimeline.dispose() - - cryptoTestData.cleanUp(commonTestHelper) } } 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 1a36adec44..7c1a097b24 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 @@ -20,6 +20,7 @@ import androidx.test.filters.LargeTest import org.amshove.kluent.shouldBeFalse import org.amshove.kluent.shouldBeTrue import org.junit.FixMethodOrder +import org.junit.Ignore import org.junit.Test import org.junit.runner.RunWith import org.junit.runners.JUnit4 @@ -33,13 +34,13 @@ 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 import org.matrix.android.sdk.common.CommonTestHelper -import org.matrix.android.sdk.common.CryptoTestHelper import org.matrix.android.sdk.common.checkSendOrder import timber.log.Timber import java.util.concurrent.CountDownLatch @RunWith(JUnit4::class) @FixMethodOrder(MethodSorters.JVM) +@Ignore("This test will be ignored until it is fixed") @LargeTest class TimelinePreviousLastForwardTest : InstrumentedTest { @@ -48,9 +49,7 @@ class TimelinePreviousLastForwardTest : InstrumentedTest { */ @Test - fun previousLastForwardTest() { - val commonTestHelper = CommonTestHelper(context()) - val cryptoTestHelper = CryptoTestHelper(commonTestHelper) + fun previousLastForwardTest() = CommonTestHelper.runCryptoTest(context()) { cryptoTestHelper, commonTestHelper -> val cryptoTestData = cryptoTestHelper.doE2ETestWithAliceAndBobInARoom(false) val aliceSession = cryptoTestData.firstSession @@ -91,7 +90,7 @@ class TimelinePreviousLastForwardTest : InstrumentedTest { } // Bob stop to sync - bobSession.stopSync() + bobSession.syncService().stopSync() val firstMessage = "First messages from Alice" // Alice sends 30 messages @@ -104,7 +103,7 @@ class TimelinePreviousLastForwardTest : InstrumentedTest { .eventId // Bob start to sync - bobSession.startSync(true) + bobSession.syncService().startSync(true) run { val lock = CountDownLatch(1) @@ -128,7 +127,7 @@ class TimelinePreviousLastForwardTest : InstrumentedTest { } // Bob stop to sync - bobSession.stopSync() + bobSession.syncService().stopSync() val secondMessage = "Second messages from Alice" // Alice sends again 30 messages @@ -139,7 +138,7 @@ class TimelinePreviousLastForwardTest : InstrumentedTest { ) // Bob start to sync - bobSession.startSync(true) + bobSession.syncService().startSync(true) run { val lock = CountDownLatch(1) @@ -232,6 +231,7 @@ class TimelinePreviousLastForwardTest : InstrumentedTest { bobTimeline.addListener(eventsListener) + bobTimeline.paginate(Timeline.Direction.FORWARDS, 50) bobTimeline.paginate(Timeline.Direction.FORWARDS, 50) commonTestHelper.await(lock) @@ -242,7 +242,5 @@ class TimelinePreviousLastForwardTest : InstrumentedTest { } bobTimeline.dispose() - - cryptoTestData.cleanUp(commonTestHelper) } } 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 42f710d7cf..59b3b14532 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 @@ -20,6 +20,7 @@ import androidx.test.filters.LargeTest import kotlinx.coroutines.runBlocking import org.amshove.kluent.internal.assertEquals import org.junit.FixMethodOrder +import org.junit.Ignore import org.junit.Test import org.junit.runner.RunWith import org.junit.runners.JUnit4 @@ -32,19 +33,17 @@ 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 -import org.matrix.android.sdk.common.CommonTestHelper -import org.matrix.android.sdk.common.CryptoTestHelper +import org.matrix.android.sdk.common.CommonTestHelper.Companion.runCryptoTest import org.matrix.android.sdk.common.TestConstants @RunWith(JUnit4::class) @FixMethodOrder(MethodSorters.JVM) @LargeTest +@Ignore class TimelineSimpleBackPaginationTest : InstrumentedTest { @Test - fun timeline_backPaginate_shouldReachEndOfTimeline() { - val commonTestHelper = CommonTestHelper(context()) - val cryptoTestHelper = CryptoTestHelper(commonTestHelper) + fun timeline_backPaginate_shouldReachEndOfTimeline() = runCryptoTest(context()) { cryptoTestHelper, commonTestHelper -> val numberOfMessagesToSent = 200 val cryptoTestData = cryptoTestHelper.doE2ETestWithAliceAndBobInARoom(false) @@ -102,6 +101,5 @@ class TimelineSimpleBackPaginationTest : InstrumentedTest { assertEquals(numberOfMessagesToSent, onlySentEvents.size) bobTimeline.dispose() - cryptoTestData.cleanUp(commonTestHelper) } } 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 02430dda74..27ea0e1c3c 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 @@ -30,8 +30,7 @@ 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 -import org.matrix.android.sdk.common.CryptoTestHelper +import org.matrix.android.sdk.common.CommonTestHelper.Companion.runCryptoTest import java.util.concurrent.CountDownLatch /** !! Not working with the new timeline @@ -47,15 +46,12 @@ class TimelineWithManyMembersTest : InstrumentedTest { private const val NUMBER_OF_MEMBERS = 6 } - private val commonTestHelper = CommonTestHelper(context()) - private val cryptoTestHelper = CryptoTestHelper(commonTestHelper) - /** * Ensures when someone sends a message to a crowded room, everyone can decrypt the message. */ @Test - fun everyone_should_decrypt_message_in_a_crowded_room() { + fun everyone_should_decrypt_message_in_a_crowded_room() = runCryptoTest(context()) { cryptoTestHelper, commonTestHelper -> val cryptoTestData = cryptoTestHelper.doE2ETestWithManyMembers(NUMBER_OF_MEMBERS) val sessionForFirstMember = cryptoTestData.firstSession @@ -75,7 +71,7 @@ class TimelineWithManyMembersTest : InstrumentedTest { val timelineForCurrentMember = roomForCurrentMember.timelineService().createTimeline(null, TimelineSettings(30)) timelineForCurrentMember.start() - session.startSync(true) + session.syncService().startSync(true) run { val lock = CountDownLatch(1) @@ -96,7 +92,7 @@ class TimelineWithManyMembersTest : InstrumentedTest { timelineForCurrentMember.addListener(eventsListener) commonTestHelper.await(lock, 600_000) } - session.stopSync() + session.syncService().stopSync() } } } 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 e17b7efbd6..7c97426c39 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 @@ -26,9 +26,8 @@ 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.CommonTestHelper.Companion.runCryptoTest import org.matrix.android.sdk.common.CryptoTestData -import org.matrix.android.sdk.common.CryptoTestHelper @RunWith(JUnit4::class) @FixMethodOrder(MethodSorters.JVM) @@ -74,9 +73,7 @@ class SearchMessagesTest : InstrumentedTest { } } - private fun doTest(block: suspend (CryptoTestData) -> SearchResult) { - val commonTestHelper = CommonTestHelper(context()) - val cryptoTestHelper = CryptoTestHelper(commonTestHelper) + private fun doTest(block: suspend (CryptoTestData) -> SearchResult) = runCryptoTest(context()) { cryptoTestHelper, commonTestHelper -> val cryptoTestData = cryptoTestHelper.doE2ETestWithAliceInARoom(false) val aliceSession = cryptoTestData.firstSession val aliceRoomId = cryptoTestData.roomId @@ -99,7 +96,5 @@ class SearchMessagesTest : InstrumentedTest { (it.event.content?.get("body") as? String)?.startsWith(MESSAGE).orFalse() }.orFalse() ) - - cryptoTestData.cleanUp(commonTestHelper) } } 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 b9760c1bfc..38136ff5ce 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 @@ -22,11 +22,13 @@ import kotlinx.coroutines.runBlocking import org.junit.Assert.assertEquals import org.junit.Assert.assertNotNull import org.junit.FixMethodOrder +import org.junit.Ignore import org.junit.Test import org.junit.runner.RunWith import org.junit.runners.JUnit4 import org.junit.runners.MethodSorters import org.matrix.android.sdk.InstrumentedTest +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.toModel import org.matrix.android.sdk.api.session.room.getStateEvent @@ -40,7 +42,7 @@ import org.matrix.android.sdk.api.session.room.model.create.CreateRoomParams import org.matrix.android.sdk.api.session.room.model.create.CreateRoomPreset import org.matrix.android.sdk.api.session.room.model.create.RoomCreateContent import org.matrix.android.sdk.api.session.space.JoinSpaceResult -import org.matrix.android.sdk.common.CommonTestHelper +import org.matrix.android.sdk.common.CommonTestHelper.Companion.runSessionTest import org.matrix.android.sdk.common.SessionTestParams @RunWith(JUnit4::class) @@ -49,8 +51,7 @@ import org.matrix.android.sdk.common.SessionTestParams class SpaceCreationTest : InstrumentedTest { @Test - fun createSimplePublicSpace() { - val commonTestHelper = CommonTestHelper(context()) + fun createSimplePublicSpace() = runSessionTest(context()) { commonTestHelper -> val session = commonTestHelper.createAccount("Hubble", SessionTestParams(true)) val roomName = "My Space" val topic = "A public space for test" @@ -73,36 +74,44 @@ class SpaceCreationTest : InstrumentedTest { // assertEquals(topic, syncedSpace.asRoom().roomSummary()?., "Room topic should be set") assertNotNull("Space should be found by Id", syncedSpace) - val creationEvent = syncedSpace!!.asRoom().getStateEvent(EventType.STATE_ROOM_CREATE) - val createContent = creationEvent?.content.toModel() + val createContent = syncedSpace!!.asRoom() + .getStateEvent(EventType.STATE_ROOM_CREATE, QueryStringValue.IsEmpty) + ?.content + ?.toModel() assertEquals("Room type should be space", RoomType.SPACE, createContent?.type) var powerLevelsContent: PowerLevelsContent? = null commonTestHelper.waitWithLatch { latch -> commonTestHelper.retryPeriodicallyWithLatch(latch) { - val toModel = syncedSpace.asRoom().getStateEvent(EventType.STATE_ROOM_POWER_LEVELS)?.content.toModel() - powerLevelsContent = toModel - toModel != null + powerLevelsContent = syncedSpace.asRoom() + .getStateEvent(EventType.STATE_ROOM_POWER_LEVELS, QueryStringValue.IsEmpty) + ?.content + ?.toModel() + powerLevelsContent != null } } assertEquals("Space-rooms should be created with a power level for events_default of 100", 100, powerLevelsContent?.eventsDefault) - val guestAccess = syncedSpace.asRoom().getStateEvent(EventType.STATE_ROOM_GUEST_ACCESS)?.content - ?.toModel()?.guestAccess + val guestAccess = syncedSpace.asRoom() + .getStateEvent(EventType.STATE_ROOM_GUEST_ACCESS, QueryStringValue.IsEmpty) + ?.content + ?.toModel() + ?.guestAccess assertEquals("Public space room should be peekable by guest", GuestAccess.CanJoin, guestAccess) - val historyVisibility = syncedSpace.asRoom().getStateEvent(EventType.STATE_ROOM_HISTORY_VISIBILITY)?.content - ?.toModel()?.historyVisibility + val historyVisibility = syncedSpace.asRoom() + .getStateEvent(EventType.STATE_ROOM_HISTORY_VISIBILITY, QueryStringValue.IsEmpty) + ?.content + ?.toModel() + ?.historyVisibility assertEquals("Public space room should be world readable", RoomHistoryVisibility.WORLD_READABLE, historyVisibility) - - commonTestHelper.signOutAndClose(session) } @Test - fun testJoinSimplePublicSpace() { - val commonTestHelper = CommonTestHelper(context()) + @Ignore + fun testJoinSimplePublicSpace() = runSessionTest(context()) { commonTestHelper -> val aliceSession = commonTestHelper.createAccount("alice", SessionTestParams(true)) val bobSession = commonTestHelper.createAccount("bob", SessionTestParams(true)) @@ -134,8 +143,7 @@ class SpaceCreationTest : InstrumentedTest { } @Test - fun testSimplePublicSpaceWithChildren() { - val commonTestHelper = CommonTestHelper(context()) + fun testSimplePublicSpaceWithChildren() = runSessionTest(context()) { commonTestHelper -> val aliceSession = commonTestHelper.createAccount("alice", SessionTestParams(true)) val bobSession = commonTestHelper.createAccount("bob", SessionTestParams(true)) @@ -204,8 +212,5 @@ class SpaceCreationTest : InstrumentedTest { // ).size // // assertEquals("Unexpected number of joined children", 1, childCount) - - commonTestHelper.signOutAndClose(aliceSession) - commonTestHelper.signOutAndClose(bobSession) } } 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 6a17cb74ad..63ca963479 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 @@ -29,8 +29,8 @@ import org.junit.runner.RunWith import org.junit.runners.JUnit4 import org.junit.runners.MethodSorters import org.matrix.android.sdk.InstrumentedTest -import org.matrix.android.sdk.api.query.ActiveSpaceFilter import org.matrix.android.sdk.api.query.QueryStringValue +import org.matrix.android.sdk.api.query.SpaceFilter 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 @@ -48,6 +48,7 @@ import org.matrix.android.sdk.api.session.room.powerlevels.PowerLevelsHelper import org.matrix.android.sdk.api.session.room.powerlevels.Role import org.matrix.android.sdk.api.session.room.roomSummaryQueryParams import org.matrix.android.sdk.common.CommonTestHelper +import org.matrix.android.sdk.common.CommonTestHelper.Companion.runSessionTest import org.matrix.android.sdk.common.SessionTestParams @RunWith(JUnit4::class) @@ -55,8 +56,7 @@ import org.matrix.android.sdk.common.SessionTestParams class SpaceHierarchyTest : InstrumentedTest { @Test - fun createCanonicalChildRelation() { - val commonTestHelper = CommonTestHelper(context()) + fun createCanonicalChildRelation() = runSessionTest(context()) { commonTestHelper -> val session = commonTestHelper.createAccount("John", SessionTestParams(true)) val spaceName = "My Space" @@ -173,8 +173,7 @@ class SpaceHierarchyTest : InstrumentedTest { // } @Test - fun testFilteringBySpace() { - val commonTestHelper = CommonTestHelper(context()) + fun testFilteringBySpace() = CommonTestHelper.runSessionTest(context()) { commonTestHelper -> val session = commonTestHelper.createAccount("John", SessionTestParams(true)) val spaceAInfo = createPublicSpace( @@ -185,12 +184,12 @@ class SpaceHierarchyTest : InstrumentedTest { ) /* val spaceBInfo = */ createPublicSpace( - session, "SpaceB", listOf( - Triple("B1", true /*auto-join*/, true/*canonical*/), - Triple("B2", true, true), - Triple("B3", true, true) - ) - ) + session, "SpaceB", listOf( + Triple("B1", true /*auto-join*/, true/*canonical*/), + Triple("B2", true, true), + Triple("B3", true, true) + ) + ) val spaceCInfo = createPublicSpace( session, "SpaceC", listOf( @@ -249,15 +248,14 @@ class SpaceHierarchyTest : InstrumentedTest { Thread.sleep(6_000) val orphansUpdate = session.roomService().getRoomSummaries(roomSummaryQueryParams { - activeSpaceFilter = ActiveSpaceFilter.ActiveSpace(null) + spaceFilter = SpaceFilter.OrphanRooms }) assertEquals("Unexpected number of orphan rooms ${orphansUpdate.map { it.name }}", 2, orphansUpdate.size) } @Test @Ignore("This test will be ignored until it is fixed") - fun testBreakCycle() { - val commonTestHelper = CommonTestHelper(context()) + fun testBreakCycle() = CommonTestHelper.runSessionTest(context()) { commonTestHelper -> val session = commonTestHelper.createAccount("John", SessionTestParams(true)) val spaceAInfo = createPublicSpace( @@ -302,8 +300,7 @@ class SpaceHierarchyTest : InstrumentedTest { } @Test - fun testLiveFlatChildren() { - val commonTestHelper = CommonTestHelper(context()) + fun testLiveFlatChildren() = CommonTestHelper.runSessionTest(context()) { commonTestHelper -> val session = commonTestHelper.createAccount("John", SessionTestParams(true)) val spaceAInfo = createPublicSpace( @@ -390,84 +387,87 @@ class SpaceHierarchyTest : InstrumentedTest { val roomIds: List ) - private fun createPublicSpace(session: Session, - spaceName: String, - childInfo: List> + private fun createPublicSpace( + session: Session, + spaceName: String, + childInfo: List> /** Name, auto-join, canonical*/ ): TestSpaceCreationResult { - val commonTestHelper = CommonTestHelper(context()) var spaceId = "" var roomIds: List = emptyList() - commonTestHelper.waitWithLatch { latch -> - spaceId = session.spaceService().createSpace(spaceName, "Test Topic", null, true) - val syncedSpace = session.spaceService().getSpace(spaceId) - val viaServers = listOf(session.sessionParams.homeServerHost ?: "") + runSessionTest(context()) { commonTestHelper -> + commonTestHelper.waitWithLatch { latch -> + spaceId = session.spaceService().createSpace(spaceName, "Test Topic", null, true) + val syncedSpace = session.spaceService().getSpace(spaceId) + val viaServers = listOf(session.sessionParams.homeServerHost ?: "") - roomIds = childInfo.map { entry -> - session.roomService().createRoom(CreateRoomParams().apply { name = entry.first }) - } - roomIds.forEachIndexed { index, roomId -> - syncedSpace!!.addChildren(roomId, viaServers, null, childInfo[index].second) - val canonical = childInfo[index].third - if (canonical != null) { - session.spaceService().setSpaceParent(roomId, spaceId, canonical, viaServers) + roomIds = childInfo.map { entry -> + session.roomService().createRoom(CreateRoomParams().apply { name = entry.first }) } + roomIds.forEachIndexed { index, roomId -> + syncedSpace!!.addChildren(roomId, viaServers, null, childInfo[index].second) + val canonical = childInfo[index].third + if (canonical != null) { + session.spaceService().setSpaceParent(roomId, spaceId, canonical, viaServers) + } + } + latch.countDown() } - latch.countDown() } return TestSpaceCreationResult(spaceId, roomIds) } - private fun createPrivateSpace(session: Session, - spaceName: String, - childInfo: List> + private fun createPrivateSpace( + session: Session, + spaceName: String, + childInfo: List> /** Name, auto-join, canonical*/ ): TestSpaceCreationResult { - val commonTestHelper = CommonTestHelper(context()) var spaceId = "" var roomIds: List = emptyList() - commonTestHelper.waitWithLatch { latch -> - spaceId = session.spaceService().createSpace(spaceName, "My Private Space", null, false) - val syncedSpace = session.spaceService().getSpace(spaceId) - val viaServers = listOf(session.sessionParams.homeServerHost ?: "") - roomIds = - childInfo.map { entry -> - val homeServerCapabilities = session - .homeServerCapabilitiesService() - .getHomeServerCapabilities() - session.roomService().createRoom(CreateRoomParams().apply { - name = entry.first - this.featurePreset = RestrictedRoomPreset( - homeServerCapabilities, - listOf( - RoomJoinRulesAllowEntry.restrictedToRoom(spaceId) - ) - ) - }) + runSessionTest(context()) { commonTestHelper -> + commonTestHelper.waitWithLatch { latch -> + spaceId = session.spaceService().createSpace(spaceName, "My Private Space", null, false) + val syncedSpace = session.spaceService().getSpace(spaceId) + val viaServers = listOf(session.sessionParams.homeServerHost ?: "") + roomIds = + childInfo.map { entry -> + val homeServerCapabilities = session + .homeServerCapabilitiesService() + .getHomeServerCapabilities() + session.roomService().createRoom(CreateRoomParams().apply { + name = entry.first + this.featurePreset = RestrictedRoomPreset( + homeServerCapabilities, + listOf( + RoomJoinRulesAllowEntry.restrictedToRoom(spaceId) + ) + ) + }) + } + roomIds.forEachIndexed { index, roomId -> + syncedSpace!!.addChildren(roomId, viaServers, null, childInfo[index].second) + val canonical = childInfo[index].third + if (canonical != null) { + session.spaceService().setSpaceParent(roomId, spaceId, canonical, viaServers) } - roomIds.forEachIndexed { index, roomId -> - syncedSpace!!.addChildren(roomId, viaServers, null, childInfo[index].second) - val canonical = childInfo[index].third - if (canonical != null) { - session.spaceService().setSpaceParent(roomId, spaceId, canonical, viaServers) } + latch.countDown() } - latch.countDown() } return TestSpaceCreationResult(spaceId, roomIds) } @Test - fun testRootSpaces() { - val commonTestHelper = CommonTestHelper(context()) + fun testRootSpaces() = runSessionTest(context()) { commonTestHelper -> val session = commonTestHelper.createAccount("John", SessionTestParams(true)) /* val spaceAInfo = */ createPublicSpace( - session, "SpaceA", listOf( - Triple("A1", true /*auto-join*/, true/*canonical*/), - Triple("A2", true, true) - ) - ) + session, "SpaceA", listOf( + Triple("A1", true /*auto-join*/, true/*canonical*/), + Triple("A2", true, true) + ) + ) val spaceBInfo = createPublicSpace( session, "SpaceB", listOf( @@ -506,13 +506,10 @@ class SpaceHierarchyTest : InstrumentedTest { } assertEquals("Unexpected number of root spaces ${rootSpaces.map { it.name }}", 2, rootSpaces.size) - - commonTestHelper.signOutAndClose(session) } @Test - fun testParentRelation() { - val commonTestHelper = CommonTestHelper(context()) + fun testParentRelation() = runSessionTest(context()) { commonTestHelper -> val aliceSession = commonTestHelper.createAccount("Alice", SessionTestParams(true)) val bobSession = commonTestHelper.createAccount("Bib", SessionTestParams(true)) @@ -572,8 +569,9 @@ class SpaceHierarchyTest : InstrumentedTest { commonTestHelper.waitWithLatch { val room = bobSession.getRoom(bobRoomId)!! val currentPLContent = room - .getStateEvent(EventType.STATE_ROOM_POWER_LEVELS) - ?.let { it.content.toModel() } + .getStateEvent(EventType.STATE_ROOM_POWER_LEVELS, QueryStringValue.IsEmpty) + ?.content + .toModel() val newPowerLevelsContent = currentPLContent ?.setUserPowerLevel(aliceSession.myUserId, Role.Admin.value) @@ -586,7 +584,7 @@ class SpaceHierarchyTest : InstrumentedTest { commonTestHelper.waitWithLatch { latch -> commonTestHelper.retryPeriodicallyWithLatch(latch) { val powerLevelsHelper = aliceSession.getRoom(bobRoomId)!! - .getStateEvent(EventType.STATE_ROOM_POWER_LEVELS) + .getStateEvent(EventType.STATE_ROOM_POWER_LEVELS, QueryStringValue.IsEmpty) ?.content ?.toModel() ?.let { PowerLevelsHelper(it) } @@ -604,8 +602,5 @@ class SpaceHierarchyTest : InstrumentedTest { bobSession.getRoomSummary(bobRoomId)?.flattenParentIds?.contains(spaceAInfo.spaceId) == true } } - - commonTestHelper.signOutAndClose(aliceSession) - commonTestHelper.signOutAndClose(bobSession) } } diff --git a/matrix-sdk-android/src/main/java/org/commonmark/ext/maths/InlineMaths.kt b/matrix-sdk-android/src/main/java/org/commonmark/ext/maths/InlineMaths.kt index 3fe8d15696..556579942b 100644 --- a/matrix-sdk-android/src/main/java/org/commonmark/ext/maths/InlineMaths.kt +++ b/matrix-sdk-android/src/main/java/org/commonmark/ext/maths/InlineMaths.kt @@ -26,14 +26,14 @@ internal class InlineMaths(private val delimiter: InlineDelimiter) : CustomNode( override fun getOpeningDelimiter(): String { return when (delimiter) { - InlineDelimiter.SINGLE_DOLLAR -> "$" + InlineDelimiter.SINGLE_DOLLAR -> "$" InlineDelimiter.ROUND_BRACKET_ESCAPED -> "\\(" } } override fun getClosingDelimiter(): String { return when (delimiter) { - InlineDelimiter.SINGLE_DOLLAR -> "$" + InlineDelimiter.SINGLE_DOLLAR -> "$" InlineDelimiter.ROUND_BRACKET_ESCAPED -> "\\)" } } 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 979201706b..953ebddcbf 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 @@ -17,6 +17,8 @@ package org.matrix.android.sdk.api import android.content.Context +import android.os.Handler +import android.os.Looper import androidx.lifecycle.ProcessLifecycleOwner import androidx.work.Configuration import androidx.work.WorkManager @@ -25,10 +27,12 @@ import com.zhuinden.monarchy.Monarchy import org.matrix.android.sdk.BuildConfig import org.matrix.android.sdk.api.auth.AuthenticationService import org.matrix.android.sdk.api.auth.HomeServerHistoryService +import org.matrix.android.sdk.api.debug.DebugService import org.matrix.android.sdk.api.legacy.LegacySessionImporter import org.matrix.android.sdk.api.network.ApiInterceptorListener import org.matrix.android.sdk.api.network.ApiPath import org.matrix.android.sdk.api.raw.RawService +import org.matrix.android.sdk.api.securestorage.SecureStorageService import org.matrix.android.sdk.api.settings.LightweightSettingsStorage import org.matrix.android.sdk.internal.SessionManager import org.matrix.android.sdk.internal.di.DaggerMatrixComponent @@ -54,6 +58,7 @@ class Matrix(context: Context, matrixConfiguration: MatrixConfiguration) { @Inject internal lateinit var legacySessionImporter: LegacySessionImporter @Inject internal lateinit var authenticationService: AuthenticationService @Inject internal lateinit var rawService: RawService + @Inject internal lateinit var debugService: DebugService @Inject internal lateinit var userAgentHolder: UserAgentHolder @Inject internal lateinit var backgroundDetectionObserver: BackgroundDetectionObserver @Inject internal lateinit var olmManager: OlmManager @@ -62,6 +67,9 @@ class Matrix(context: Context, matrixConfiguration: MatrixConfiguration) { @Inject internal lateinit var apiInterceptor: ApiInterceptor @Inject internal lateinit var matrixWorkerFactory: MatrixWorkerFactory @Inject internal lateinit var lightweightSettingsStorage: LightweightSettingsStorage + @Inject internal lateinit var secureStorageService: SecureStorageService + + private val uiHandler = Handler(Looper.getMainLooper()) init { val appContext = context.applicationContext @@ -74,7 +82,9 @@ class Matrix(context: Context, matrixConfiguration: MatrixConfiguration) { .build() WorkManager.initialize(appContext, configuration) } - ProcessLifecycleOwner.get().lifecycle.addObserver(backgroundDetectionObserver) + uiHandler.post { + ProcessLifecycleOwner.get().lifecycle.addObserver(backgroundDetectionObserver) + } } /** @@ -93,6 +103,11 @@ class Matrix(context: Context, matrixConfiguration: MatrixConfiguration) { */ fun rawService() = rawService + /** + * Return the DebugService. + */ + fun debugService() = debugService + /** * Return the LightweightSettingsStorage. */ @@ -108,6 +123,11 @@ class Matrix(context: Context, matrixConfiguration: MatrixConfiguration) { */ fun legacySessionImporter() = legacySessionImporter + /** + * Returns the SecureStorageService used to encrypt and decrypt sensitive data. + */ + fun secureStorageService(): SecureStorageService = secureStorageService + /** * Get the worker factory. The returned value has to be provided to `WorkConfiguration.Builder()`. */ diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/MatrixConfiguration.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/MatrixConfiguration.kt index 21106fba6c..893e90fb3e 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/MatrixConfiguration.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/MatrixConfiguration.kt @@ -17,6 +17,7 @@ package org.matrix.android.sdk.api import okhttp3.ConnectionSpec +import okhttp3.Interceptor import org.matrix.android.sdk.api.crypto.MXCryptoConfig import java.net.Proxy @@ -65,4 +66,8 @@ data class MatrixConfiguration( * Thread messages default enable/disabled value. */ val threadMessagesEnabledDefault: Boolean = false, + /** + * List of network interceptors, they will be added when building an OkHttp client. + */ + val networkInterceptors: List = emptyList(), ) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/RoomDisplayNameFallbackProvider.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/RoomDisplayNameFallbackProvider.kt index a34dbcc196..3c376b55ee 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/RoomDisplayNameFallbackProvider.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/RoomDisplayNameFallbackProvider.kt @@ -16,6 +16,14 @@ package org.matrix.android.sdk.api +/** + * This interface exists to let the implementation provide localized room display name fallback. + * The methods can be called when the room has no name, i.e. its `m.room.name` state event does not exist or + * the name in it is an empty String. + * It allows the SDK to store the room name fallback into the local storage and so let the client do + * queries on the room name. + * *Limitation*: if the locale of the device changes, the methods will not be called again. + */ interface RoomDisplayNameFallbackProvider { fun getNameForRoomInvite(): String fun getNameForEmptyRoom(isDirect: Boolean, leftMemberNames: List): String diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/auth/AuthenticationService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/auth/AuthenticationService.kt index 5a19df90c4..5ae70e1978 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/auth/AuthenticationService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/auth/AuthenticationService.kt @@ -28,9 +28,11 @@ import org.matrix.android.sdk.api.session.Session * This interface defines methods to authenticate or to create an account to a matrix server. */ interface AuthenticationService { + /** * Request the supported login flows for this homeserver. - * This is the first method to call to be able to get a wizard to login or to create an account + * This is the first method to call to be able to get a wizard to login or to create an account. + * @param homeServerConnectionConfig contains the homeserver URL to login to, a wellKnown lookup will be attempted. */ suspend fun getLoginFlow(homeServerConnectionConfig: HomeServerConnectionConfig): LoginFlowResult @@ -66,7 +68,7 @@ interface AuthenticationService { /** * True when login and password has been sent with success to the homeserver. */ - val isRegistrationStarted: Boolean + fun isRegistrationStarted(): Boolean /** * Cancel pending login or pending registration. @@ -93,14 +95,18 @@ interface AuthenticationService { /** * Create a session after a SSO successful login. */ - suspend fun createSessionFromSso(homeServerConnectionConfig: HomeServerConnectionConfig, - credentials: Credentials): Session + suspend fun createSessionFromSso( + homeServerConnectionConfig: HomeServerConnectionConfig, + credentials: Credentials + ): Session /** * Perform a wellknown request, using the domain from the matrixId. */ - suspend fun getWellKnownData(matrixId: String, - homeServerConnectionConfig: HomeServerConnectionConfig?): WellknownResult + suspend fun getWellKnownData( + matrixId: String, + homeServerConnectionConfig: HomeServerConnectionConfig? + ): WellknownResult /** * Authenticate with a matrixId and a password. @@ -111,9 +117,11 @@ interface AuthenticationService { * @param initialDeviceName the initial device name * @param deviceId the device id, optional. If not provided or null, the server will generate one. */ - suspend fun directAuthentication(homeServerConnectionConfig: HomeServerConnectionConfig, - matrixId: String, - password: String, - initialDeviceName: String, - deviceId: String? = null): Session + suspend fun directAuthentication( + homeServerConnectionConfig: HomeServerConnectionConfig, + matrixId: String, + password: String, + initialDeviceName: String, + deviceId: String? = null + ): Session } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/auth/converter.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/auth/converter.kt index 80630bc4e7..e2f16ceee8 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/auth/converter.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/auth/converter.kt @@ -41,8 +41,10 @@ import org.matrix.android.sdk.api.auth.registration.TermPolicies * @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 { +fun TermPolicies.toLocalizedLoginTerms( + userLanguage: String, + defaultLanguage: String = "en" +): List { val result = ArrayList() val policies = get("policies") @@ -67,8 +69,8 @@ fun TermPolicies.toLocalizedLoginTerms(userLanguage: String, // Search for language policy.keys.forEach { policyKey -> when (policyKey) { - "version" -> Unit // Ignore - userLanguage -> { + "version" -> Unit // Ignore + userLanguage -> { // We found the data for the user language userLanguageUrlAndName = extractUrlAndName(policy[policyKey]) } @@ -76,7 +78,7 @@ fun TermPolicies.toLocalizedLoginTerms(userLanguage: String, // We found default language defaultLanguageUrlAndName = extractUrlAndName(policy[policyKey]) } - else -> { + else -> { if (firstUrlAndName == null) { // Get at least some data firstUrlAndName = extractUrlAndName(policy[policyKey]) @@ -87,7 +89,7 @@ fun TermPolicies.toLocalizedLoginTerms(userLanguage: String, // Copy found language data by priority when { - userLanguageUrlAndName != null -> { + userLanguageUrlAndName != null -> { localizedFlowDataLoginTermsLocalizedUrl = userLanguageUrlAndName!!.url localizedFlowDataLoginTermsLocalizedName = userLanguageUrlAndName!!.name } @@ -95,7 +97,7 @@ fun TermPolicies.toLocalizedLoginTerms(userLanguage: String, localizedFlowDataLoginTermsLocalizedUrl = defaultLanguageUrlAndName!!.url localizedFlowDataLoginTermsLocalizedName = defaultLanguageUrlAndName!!.name } - firstUrlAndName != null -> { + firstUrlAndName != null -> { localizedFlowDataLoginTermsLocalizedUrl = firstUrlAndName!!.url localizedFlowDataLoginTermsLocalizedName = firstUrlAndName!!.name } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/auth/data/LoginFlowResult.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/auth/data/LoginFlowResult.kt index 7d1407c0d8..5b6c1897bf 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/auth/data/LoginFlowResult.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/auth/data/LoginFlowResult.kt @@ -21,5 +21,6 @@ data class LoginFlowResult( val ssoIdentityProviders: List?, val isLoginAndRegistrationSupported: Boolean, val homeServerUrl: String, - val isOutdatedHomeserver: Boolean + val isOutdatedHomeserver: Boolean, + val isLogoutDevicesSupported: Boolean ) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/auth/data/SsoIdentityProvider.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/auth/data/SsoIdentityProvider.kt index a0733dda97..773f5a8cc4 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/auth/data/SsoIdentityProvider.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/auth/data/SsoIdentityProvider.kt @@ -66,17 +66,17 @@ data class SsoIdentityProvider( private fun toPriority(): Int { return when (brand) { // We are on Android, so user is more likely to have a Google account - BRAND_GOOGLE -> 5 + BRAND_GOOGLE -> 5 // Facebook is also an important SSO provider BRAND_FACEBOOK -> 4 // Twitter is more for professionals - BRAND_TWITTER -> 3 + BRAND_TWITTER -> 3 // Here it's very for techie people BRAND_GITHUB, - BRAND_GITLAB -> 2 + BRAND_GITLAB -> 2 // And finally, if the account has been created with an iPhone... - BRAND_APPLE -> 1 - else -> 0 + BRAND_APPLE -> 1 + else -> 0 } } } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/auth/login/LoginWizard.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/auth/login/LoginWizard.kt index 3232025de3..145cdbdc22 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/auth/login/LoginWizard.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/auth/login/LoginWizard.kt @@ -39,10 +39,12 @@ interface LoginWizard { * @param deviceId the device id, optional. If not provided or null, the server will generate one. * @return a [Session] if the login is successful */ - suspend fun login(login: String, - password: String, - initialDeviceName: String, - deviceId: String? = null): Session + suspend fun login( + login: String, + password: String, + initialDeviceName: String, + deviceId: String? = null + ): Session /** * Exchange a login token to an access token. @@ -63,14 +65,16 @@ interface LoginWizard { * [resetPasswordMailConfirmed] is successfully called. * * @param email an email previously associated to the account the user wants the password to be reset. - * @param newPassword the desired new password */ - suspend fun resetPassword(email: String, - newPassword: String) + suspend fun resetPassword(email: String) /** * Confirm the new password, once the user has checked their email * When this method succeed, tha account password will be effectively modified. + * + * @param newPassword the desired new password. + * @param logoutAllDevices defaults to true, all devices will be logged out. False values will only be taken into account + * if [org.matrix.android.sdk.api.auth.data.LoginFlowResult.isLogoutDevicesSupported] is true. */ - suspend fun resetPasswordMailConfirmed() + suspend fun resetPasswordMailConfirmed(newPassword: String, logoutAllDevices: Boolean = true) } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/auth/registration/RegistrationFlowResponse.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/auth/registration/RegistrationFlowResponse.kt index 1252e93b84..98542d2086 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/auth/registration/RegistrationFlowResponse.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/auth/registration/RegistrationFlowResponse.kt @@ -88,15 +88,15 @@ fun RegistrationFlowResponse.toFlowResult(): FlowResult { val isMandatory = flows?.all { type in it.stages.orEmpty() } == true val stage = when (type) { - LoginFlowTypes.RECAPTCHA -> Stage.ReCaptcha( + LoginFlowTypes.RECAPTCHA -> Stage.ReCaptcha( isMandatory, ((params?.get(type) as? Map<*, *>)?.get("public_key") as? String) ?: "" ) - LoginFlowTypes.DUMMY -> Stage.Dummy(isMandatory) - LoginFlowTypes.TERMS -> Stage.Terms(isMandatory, params?.get(type) as? TermPolicies ?: emptyMap()) + LoginFlowTypes.DUMMY -> Stage.Dummy(isMandatory) + LoginFlowTypes.TERMS -> Stage.Terms(isMandatory, params?.get(type) as? TermPolicies ?: emptyMap()) LoginFlowTypes.EMAIL_IDENTITY -> Stage.Email(isMandatory) - LoginFlowTypes.MSISDN -> Stage.Msisdn(isMandatory) - else -> Stage.Other(isMandatory, type, (params?.get(type) as? Map<*, *>)) + LoginFlowTypes.MSISDN -> Stage.Msisdn(isMandatory) + else -> Stage.Other(isMandatory, type, (params?.get(type) as? Map<*, *>)) } if (type in completedStages.orEmpty()) { diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/auth/registration/RegistrationResult.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/auth/registration/RegistrationResult.kt index 439b4beb41..9e6b2b3ad9 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/auth/registration/RegistrationResult.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/auth/registration/RegistrationResult.kt @@ -18,13 +18,31 @@ package org.matrix.android.sdk.api.auth.registration import org.matrix.android.sdk.api.session.Session -// Either a session or an object containing data about registration stages +/** + * Either a session or an object containing data about registration stages. + */ sealed class RegistrationResult { + /** + * The registration is successful, the [Session] is provided. + */ data class Success(val session: Session) : RegistrationResult() + + /** + * The registration still miss some steps. See [FlowResult] to know the details. + */ data class FlowResponse(val flowResult: FlowResult) : RegistrationResult() } +/** + * Information about the missing and completed [Stage]. + */ data class FlowResult( + /** + * List of missing stages. + */ val missingStages: List, + /** + * List of completed stages. + */ val completedStages: List ) 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 0cda64499f..995fd27ace 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 @@ -54,9 +54,11 @@ interface RegistrationWizard { * @param password the desired password * @param initialDeviceDisplayName the device display name */ - suspend fun createAccount(userName: String?, - password: String?, - initialDeviceDisplayName: String?): RegistrationResult + suspend fun createAccount( + userName: String?, + password: String?, + initialDeviceDisplayName: String? + ): RegistrationResult /** * Perform the "m.login.recaptcha" stage. @@ -109,14 +111,14 @@ interface RegistrationWizard { suspend fun checkIfEmailHasBeenValidated(delayMillis: Long): RegistrationResult /** - * This is the current ThreePid, waiting for validation. The SDK will store it in database, so it can be + * Returns the current ThreePid, waiting for validation. The SDK will store it in database, so it can be * restored even if the app has been killed during the registration */ - val currentThreePid: String? + fun getCurrentThreePid(): String? /** - * True when login and password have been sent with success to the homeserver, i.e. [createAccount] has been + * Return true when login and password have been sent with success to the homeserver, i.e. [createAccount] has been * called successfully. */ - val isRegistrationStarted: Boolean + fun isRegistrationStarted(): Boolean } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/auth/registration/Stage.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/auth/registration/Stage.kt index c21b667cf7..281b0c2808 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/auth/registration/Stage.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/auth/registration/Stage.kt @@ -16,25 +16,40 @@ package org.matrix.android.sdk.api.auth.registration +/** + * Registration stages. + */ sealed class Stage(open val mandatory: Boolean) { - // m.login.recaptcha + /** + * m.login.recaptcha stage. + */ data class ReCaptcha(override val mandatory: Boolean, val publicKey: String) : Stage(mandatory) - // m.login.email.identity + /** + * m.login.email.identity stage. + */ data class Email(override val mandatory: Boolean) : Stage(mandatory) - // m.login.msisdn + /** + * m.login.msisdn stage. + */ data class Msisdn(override val mandatory: Boolean) : Stage(mandatory) - // m.login.dummy, can be mandatory if there is no other stages. In this case the account cannot be created by just sending a username - // and a password, the dummy stage has to be done + /** + * m.login.dummy, can be mandatory if there is no other stages. In this case the account cannot be created by just sending a username + * and a password, the dummy stage has to be done. + */ data class Dummy(override val mandatory: Boolean) : Stage(mandatory) - // Undocumented yet: m.login.terms + /** + * Undocumented yet: m.login.terms stage. + */ data class Terms(override val mandatory: Boolean, val policies: TermPolicies) : Stage(mandatory) - // For unknown stages + /** + * For unknown stages. + */ data class Other(override val mandatory: Boolean, val type: String, val params: Map<*, *>?) : Stage(mandatory) } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/auth/wellknown/WellknownResult.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/auth/wellknown/WellknownResult.kt index 56257db79c..ea5570db1c 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/auth/wellknown/WellknownResult.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/auth/wellknown/WellknownResult.kt @@ -26,9 +26,11 @@ sealed class WellknownResult { * Retrieve the specific piece of information from the user in a way which fits within the existing client user experience, * if the client is inclined to do so. Failure can take place instead if no good user experience for this is possible at this point. */ - data class Prompt(val homeServerUrl: String, - val identityServerUrl: String?, - val wellKnown: WellKnown) : WellknownResult() + data class Prompt( + val homeServerUrl: String, + val identityServerUrl: String?, + val wellKnown: WellKnown + ) : WellknownResult() /** * Stop the current auto-discovery mechanism. If no more auto-discovery mechanisms are available, diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/cache/CacheStrategy.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/cache/CacheStrategy.kt index 2880d851d6..ddf76d6e42 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/cache/CacheStrategy.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/cache/CacheStrategy.kt @@ -17,13 +17,19 @@ package org.matrix.android.sdk.api.cache sealed class CacheStrategy { - // Data is always fetched from the server + /** + * Data is always fetched from the server. + */ object NoCache : CacheStrategy() - // Once data is retrieved, it is stored for the provided amount of time. - // In case of error, and if strict is set to false, the cache can be returned if available + /** + * Once data is retrieved, it is stored for the provided amount of time. + * In case of error, and if strict is set to false, the cache can be returned if available + */ data class TtlCache(val validityDurationInMillis: Long, val strict: Boolean) : CacheStrategy() - // Once retrieved, the data is stored in cache and will be always get from the cache + /** + * Once retrieved, the data is stored in cache and will be always get from the cache. + */ object InfiniteCache : CacheStrategy() } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/debug/DebugService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/debug/DebugService.kt new file mode 100644 index 0000000000..d0cee08831 --- /dev/null +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/debug/DebugService.kt @@ -0,0 +1,34 @@ +/* + * Copyright (c) 2022 The Matrix.org Foundation C.I.C. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.matrix.android.sdk.api.debug + +import io.realm.RealmConfiguration + +/** + * Useful methods to access to some private data managed by the SDK. + */ +interface DebugService { + /** + * Get all the available Realm Configuration. + */ + fun getAllRealmConfigurations(): List + + /** + * Prints out info on DB size to logcat. + */ + fun logDbUsageInfo() +} diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/extensions/Strings.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/extensions/Strings.kt index 5e1350e327..9f979098f8 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/extensions/Strings.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/extensions/Strings.kt @@ -19,7 +19,7 @@ package org.matrix.android.sdk.api.extensions fun CharSequence.ensurePrefix(prefix: CharSequence): CharSequence { return when { startsWith(prefix) -> this - else -> "$prefix$this" + else -> "$prefix$this" } } @@ -27,3 +27,8 @@ fun CharSequence.ensurePrefix(prefix: CharSequence): CharSequence { * Append a new line and then the provided string. */ fun StringBuilder.appendNl(str: String) = append("\n").append(str) + +/** + * Returns null if the string is empty. + */ +fun String.ensureNotEmpty() = ifEmpty { null } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/failure/Failure.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/failure/Failure.kt index be139fd82b..7d4f553bed 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/failure/Failure.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/failure/Failure.kt @@ -37,7 +37,9 @@ sealed class Failure(cause: Throwable? = null) : Throwable(cause = cause) { data class ServerError(val error: MatrixError, val httpCode: Int) : Failure(RuntimeException(error.toString())) object SuccessError : Failure(RuntimeException(RuntimeException("SuccessResult is false"))) - // When server send an error, but it cannot be interpreted as a MatrixError + /** + * When server send an error, but it cannot be interpreted as a MatrixError. + */ data class OtherServerError(val errorBody: String, val httpCode: Int) : Failure(RuntimeException("HTTP $httpCode: $errorBody")) data class RegistrationFlowError(val registrationFlowResponse: RegistrationFlowResponse) : Failure(RuntimeException(registrationFlowResponse.toString())) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/network/ssl/Fingerprint.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/network/ssl/Fingerprint.kt index 93e93fd292..2fc04013f9 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/network/ssl/Fingerprint.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/network/ssl/Fingerprint.kt @@ -36,7 +36,7 @@ data class Fingerprint( internal fun matchesCert(cert: X509Certificate): Boolean { val o: Fingerprint? = when (hashType) { HashType.SHA256 -> newSha256Fingerprint(cert) - HashType.SHA1 -> newSha1Fingerprint(cert) + HashType.SHA1 -> newSha1Fingerprint(cert) } return equals(o) } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/query/QueryStringValue.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/query/QueryStringValue.kt index 368ff98661..d3f6ec2287 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/query/QueryStringValue.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/query/QueryStringValue.kt @@ -16,24 +16,61 @@ package org.matrix.android.sdk.api.query +/** + * Only a subset of [QueryStringValue] are applicable to query the `stateKey` of a state event. + */ +sealed interface QueryStateEventValue + /** * Basic query language. All these cases are mutually exclusive. */ sealed interface QueryStringValue { - sealed interface ContentQueryStringValue : QueryStringValue { + /** + * No condition, i.e. there will be no test on the tested field. + */ + object NoCondition : QueryStringValue + + /** + * The tested field has to be null. + */ + object IsNull : QueryStringValue + + /** + * The tested field has to be not null. + */ + object IsNotNull : QueryStringValue, QueryStateEventValue + + /** + * The tested field has to be empty. + */ + object IsEmpty : QueryStringValue, QueryStateEventValue + + /** + * The tested field has to be not empty. + */ + object IsNotEmpty : QueryStringValue, QueryStateEventValue + + /** + * Interface to check String content. + */ + sealed interface ContentQueryStringValue : QueryStringValue, QueryStateEventValue { val string: String val case: Case } - object NoCondition : QueryStringValue - object IsNull : QueryStringValue - object IsNotNull : QueryStringValue - object IsEmpty : QueryStringValue - object IsNotEmpty : QueryStringValue - + /** + * The tested field must match the [string]. + */ data class Equals(override val string: String, override val case: Case = Case.SENSITIVE) : ContentQueryStringValue + + /** + * The tested field must contain the [string]. + */ data class Contains(override val string: String, override val case: Case = Case.SENSITIVE) : ContentQueryStringValue + /** + * Case enum for [ContentQueryStringValue]. + */ enum class Case { /** * Match query sensitive to case. diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/query/RoomCategoryFilter.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/query/RoomCategoryFilter.kt index c8ccc4c8a3..c2117adbd3 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/query/RoomCategoryFilter.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/query/RoomCategoryFilter.kt @@ -16,9 +16,23 @@ package org.matrix.android.sdk.api.query +/** + * To filter by Room category. + * @see [org.matrix.android.sdk.api.session.room.RoomSummaryQueryParams] + */ enum class RoomCategoryFilter { + /** + * Get only the DM, i.e. the rooms referenced in `m.direct` account data. + */ ONLY_DM, + + /** + * Get only the Room, not the DM, i.e. the rooms not referenced in `m.direct` account data. + */ ONLY_ROOMS, + + /** + * Get the room with non-0 notifications. + */ ONLY_WITH_NOTIFICATIONS, - ALL } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/query/RoomTagQueryFilter.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/query/RoomTagQueryFilter.kt index 613916bc18..73947f8f7a 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/query/RoomTagQueryFilter.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/query/RoomTagQueryFilter.kt @@ -16,8 +16,22 @@ package org.matrix.android.sdk.api.query +/** + * Filter room by their tag. + * @see [org.matrix.android.sdk.api.session.room.RoomSummaryQueryParams] + * @see [org.matrix.android.sdk.api.session.room.model.tag.RoomTag] + */ data class RoomTagQueryFilter( + /** + * Set to true to get the rooms which have the tag "m.favourite". + */ val isFavorite: Boolean?, + /** + * Set to true to get the rooms which have the tag "m.lowpriority". + */ val isLowPriority: Boolean?, - val isServerNotice: Boolean? + /** + * Set to true to get the rooms which have the tag "m.server_notice". + */ + val isServerNotice: Boolean?, ) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/query/SpaceFilter.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/query/SpaceFilter.kt new file mode 100644 index 0000000000..6383412ffb --- /dev/null +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/query/SpaceFilter.kt @@ -0,0 +1,43 @@ +/* + * Copyright (c) 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.api.query + +/** + * Filter to be used to do room queries regarding the space hierarchy. + * @see [org.matrix.android.sdk.api.session.room.RoomSummaryQueryParams] + */ +sealed interface SpaceFilter { + /** + * Used to get all the rooms that are not in any space. + */ + object OrphanRooms : SpaceFilter + + /** + * Used to get all the rooms that have the provided space in their parent hierarchy. + */ + data class ActiveSpace(val spaceId: String) : SpaceFilter + + /** + * Used to get all the rooms that do not have the provided space in their parent hierarchy. + */ + data class ExcludeSpace(val spaceId: String) : SpaceFilter +} + +/** + * Return a [SpaceFilter.ActiveSpace] if the String is not null, or [SpaceFilter.OrphanRooms]. + */ +fun String?.toActiveSpaceOrOrphanRooms(): SpaceFilter = this?.let { SpaceFilter.ActiveSpace(it) } ?: SpaceFilter.OrphanRooms diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/securestorage/SecretStoringUtils.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/securestorage/SecretStoringUtils.kt similarity index 81% rename from matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/securestorage/SecretStoringUtils.kt rename to matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/securestorage/SecretStoringUtils.kt index 07a5cbe5a0..bd2a1078b2 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/securestorage/SecretStoringUtils.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/securestorage/SecretStoringUtils.kt @@ -16,7 +16,7 @@ @file:Suppress("DEPRECATION") -package org.matrix.android.sdk.internal.session.securestorage +package org.matrix.android.sdk.api.securestorage import android.annotation.SuppressLint import android.content.Context @@ -25,7 +25,7 @@ import android.security.KeyPairGeneratorSpec import android.security.keystore.KeyGenParameterSpec import android.security.keystore.KeyProperties import androidx.annotation.RequiresApi -import org.matrix.android.sdk.internal.util.system.BuildVersionSdkIntProvider +import org.matrix.android.sdk.api.util.BuildVersionSdkIntProvider import timber.log.Timber import java.io.ByteArrayInputStream import java.io.ByteArrayOutputStream @@ -80,9 +80,11 @@ import javax.security.auth.x500.X500Principal * Important: Keys stored in the keystore can be wiped out (depends of the OS version, like for example if you * add a pin or change the schema); So you might and with a useless pile of bytes. */ -internal class SecretStoringUtils @Inject constructor( +class SecretStoringUtils @Inject constructor( private val context: Context, - private val buildVersionSdkIntProvider: BuildVersionSdkIntProvider + private val keyStore: KeyStore, + private val buildVersionSdkIntProvider: BuildVersionSdkIntProvider, + private val keyNeedsUserAuthentication: Boolean = false, ) { companion object { @@ -94,14 +96,24 @@ internal class SecretStoringUtils @Inject constructor( private const val FORMAT_1: Byte = 1 } - private val keyStore: KeyStore by lazy { - KeyStore.getInstance(ANDROID_KEY_STORE).apply { - load(null) - } - } - private val secureRandom = SecureRandom() + /** + * Allows creation of the crypto keys associated witht he [alias] before encrypting some value with it. + * @return A [KeyStore.Entry] with the keys. + */ + @SuppressLint("NewApi") + fun ensureKey(alias: String): KeyStore.Entry { + when { + buildVersionSdkIntProvider.get() >= Build.VERSION_CODES.M -> getOrGenerateSymmetricKeyForAliasM(alias) + else -> getOrGenerateKeyPairForAlias(alias).privateKey + } + return keyStore.getEntry(alias, null) + } + + /** + * Deletes the key associated with the [keyAlias] and logs any [KeyStoreException] that could happen. + */ fun safeDeleteKey(keyAlias: String) { try { keyStore.deleteEntry(keyAlias) @@ -121,25 +133,25 @@ internal class SecretStoringUtils @Inject constructor( */ @SuppressLint("NewApi") @Throws(Exception::class) - fun securelyStoreString(secret: String, keyAlias: String): ByteArray { + fun securelyStoreBytes(secret: ByteArray, keyAlias: String): ByteArray { return when { - buildVersionSdkIntProvider.get() >= Build.VERSION_CODES.M -> encryptStringM(secret, keyAlias) - else -> encryptString(secret, keyAlias) + buildVersionSdkIntProvider.get() >= Build.VERSION_CODES.M -> encryptBytesM(secret, keyAlias) + else -> encryptBytes(secret, keyAlias) } } /** - * Decrypt a secret that was encrypted by #securelyStoreString(). + * Decrypt a secret that was encrypted by [securelyStoreBytes]. */ @SuppressLint("NewApi") @Throws(Exception::class) - fun loadSecureSecret(encrypted: ByteArray, keyAlias: String): String { + fun loadSecureSecretBytes(encrypted: ByteArray, keyAlias: String): ByteArray { encrypted.inputStream().use { inputStream -> // First get the format return when (val format = inputStream.read().toByte()) { - FORMAT_API_M -> decryptStringM(inputStream, keyAlias) - FORMAT_1 -> decryptString(inputStream, keyAlias) - else -> throw IllegalArgumentException("Unknown format $format") + FORMAT_API_M -> decryptBytesM(inputStream, keyAlias) + FORMAT_1 -> decryptBytes(inputStream, keyAlias) + else -> throw IllegalArgumentException("Unknown format $format") } } } @@ -148,7 +160,7 @@ internal class SecretStoringUtils @Inject constructor( fun securelyStoreObject(any: Any, keyAlias: String, output: OutputStream) { when { buildVersionSdkIntProvider.get() >= Build.VERSION_CODES.M -> saveSecureObjectM(keyAlias, output, any) - else -> saveSecureObject(keyAlias, output, any) + else -> saveSecureObject(keyAlias, output, any) } } @@ -157,11 +169,27 @@ internal class SecretStoringUtils @Inject constructor( // First get the format return when (val format = inputStream.read().toByte()) { FORMAT_API_M -> loadSecureObjectM(keyAlias, inputStream) - FORMAT_1 -> loadSecureObject(keyAlias, inputStream) - else -> throw IllegalArgumentException("Unknown format $format") + FORMAT_1 -> loadSecureObject(keyAlias, inputStream) + else -> throw IllegalArgumentException("Unknown format $format") } } + fun getEncryptCipher(alias: String): Cipher { + val key = when (val keyEntry = ensureKey(alias)) { + is KeyStore.SecretKeyEntry -> keyEntry.secretKey + is KeyStore.PrivateKeyEntry -> keyEntry.certificate.publicKey + else -> throw IllegalStateException("Unknown KeyEntry type.") + } + val cipherMode = when { + buildVersionSdkIntProvider.get() >= Build.VERSION_CODES.M -> AES_MODE + else -> RSA_MODE + } + val cipher = Cipher.getInstance(cipherMode) + cipher.init(Cipher.ENCRYPT_MODE, key) + return cipher + } + + @SuppressLint("NewApi") @RequiresApi(Build.VERSION_CODES.M) private fun getOrGenerateSymmetricKeyForAliasM(alias: String): SecretKey { val secretKeyEntry = (keyStore.getEntry(alias, null) as? KeyStore.SecretKeyEntry) @@ -176,6 +204,13 @@ internal class SecretStoringUtils @Inject constructor( .setBlockModes(KeyProperties.BLOCK_MODE_GCM) .setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_NONE) .setKeySize(128) + .apply { + setUserAuthenticationRequired(keyNeedsUserAuthentication) + if (buildVersionSdkIntProvider.get() >= Build.VERSION_CODES.N) { + setInvalidatedByBiometricEnrollment(true) + } + } + .setUserAuthenticationRequired(keyNeedsUserAuthentication) .build() generator.init(keyGenSpec) return generator.generateKey() @@ -216,19 +251,16 @@ internal class SecretStoringUtils @Inject constructor( } @RequiresApi(Build.VERSION_CODES.M) - private fun encryptStringM(text: String, keyAlias: String): ByteArray { - val secretKey = getOrGenerateSymmetricKeyForAliasM(keyAlias) - - val cipher = Cipher.getInstance(AES_MODE) - cipher.init(Cipher.ENCRYPT_MODE, secretKey) + private fun encryptBytesM(byteArray: ByteArray, keyAlias: String): ByteArray { + val cipher = getEncryptCipher(keyAlias) val iv = cipher.iv // we happen the iv to the final result - val encryptedBytes: ByteArray = cipher.doFinal(text.toByteArray(Charsets.UTF_8)) + val encryptedBytes: ByteArray = cipher.doFinal(byteArray) return formatMMake(iv, encryptedBytes) } @RequiresApi(Build.VERSION_CODES.M) - private fun decryptStringM(inputStream: InputStream, keyAlias: String): String { + private fun decryptBytesM(inputStream: InputStream, keyAlias: String): ByteArray { val (iv, encryptedText) = formatMExtract(inputStream) val secretKey = getOrGenerateSymmetricKeyForAliasM(keyAlias) @@ -237,10 +269,10 @@ internal class SecretStoringUtils @Inject constructor( val spec = GCMParameterSpec(128, iv) cipher.init(Cipher.DECRYPT_MODE, secretKey, spec) - return String(cipher.doFinal(encryptedText), Charsets.UTF_8) + return cipher.doFinal(encryptedText) } - private fun encryptString(text: String, keyAlias: String): ByteArray { + private fun encryptBytes(byteArray: ByteArray, keyAlias: String): ByteArray { // we generate a random symmetric key val key = ByteArray(16) secureRandom.nextBytes(key) @@ -252,12 +284,12 @@ internal class SecretStoringUtils @Inject constructor( val cipher = Cipher.getInstance(AES_MODE) cipher.init(Cipher.ENCRYPT_MODE, sKey) val iv = cipher.iv - val encryptedBytes: ByteArray = cipher.doFinal(text.toByteArray(Charsets.UTF_8)) + val encryptedBytes: ByteArray = cipher.doFinal(byteArray) return format1Make(encryptedKey, iv, encryptedBytes) } - private fun decryptString(inputStream: InputStream, keyAlias: String): String { + private fun decryptBytes(inputStream: InputStream, keyAlias: String): ByteArray { val (encryptedKey, iv, encrypted) = format1Extract(inputStream) // we need to decrypt the key @@ -266,16 +298,13 @@ internal class SecretStoringUtils @Inject constructor( val spec = GCMParameterSpec(128, iv) cipher.init(Cipher.DECRYPT_MODE, SecretKeySpec(sKeyBytes, "AES"), spec) - return String(cipher.doFinal(encrypted), Charsets.UTF_8) + return cipher.doFinal(encrypted) } @RequiresApi(Build.VERSION_CODES.M) @Throws(IOException::class) private fun saveSecureObjectM(keyAlias: String, output: OutputStream, writeObject: Any) { - val secretKey = getOrGenerateSymmetricKeyForAliasM(keyAlias) - - val cipher = Cipher.getInstance(AES_MODE) - cipher.init(Cipher.ENCRYPT_MODE, secretKey/*, spec*/) + val cipher = getEncryptCipher(keyAlias) val iv = cipher.iv val bos1 = ByteArrayOutputStream() @@ -362,10 +391,8 @@ internal class SecretStoringUtils @Inject constructor( @Throws(Exception::class) private fun rsaEncrypt(alias: String, secret: ByteArray): ByteArray { - val privateKeyEntry = getOrGenerateKeyPairForAlias(alias) // Encrypt the text - val inputCipher = Cipher.getInstance(RSA_MODE) - inputCipher.init(Cipher.ENCRYPT_MODE, privateKeyEntry.certificate.publicKey) + val inputCipher = getEncryptCipher(alias) val outputStream = ByteArrayOutputStream() CipherOutputStream(outputStream, inputCipher).use { diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/securestorage/SecureStorageModule.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/securestorage/SecureStorageModule.kt new file mode 100644 index 0000000000..37a40fd677 --- /dev/null +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/securestorage/SecureStorageModule.kt @@ -0,0 +1,45 @@ +/* + * 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.securestorage + +import android.content.Context +import dagger.Binds +import dagger.Module +import dagger.Provides +import org.matrix.android.sdk.api.util.BuildVersionSdkIntProvider +import org.matrix.android.sdk.api.util.DefaultBuildVersionSdkIntProvider +import java.security.KeyStore + +@Module +internal abstract class SecureStorageModule { + + @Module + companion object { + @Provides + fun provideKeyStore(): KeyStore = KeyStore.getInstance("AndroidKeyStore").also { it.load(null) } + + @Provides + fun provideSecretStoringUtils( + context: Context, + keyStore: KeyStore, + buildVersionSdkIntProvider: BuildVersionSdkIntProvider, + ): SecretStoringUtils = SecretStoringUtils(context, keyStore, buildVersionSdkIntProvider) + } + + @Binds + abstract fun bindBuildVersionSdkIntProvider(provider: DefaultBuildVersionSdkIntProvider): BuildVersionSdkIntProvider +} diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/securestorage/SecureStorageService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/securestorage/SecureStorageService.kt similarity index 93% rename from matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/securestorage/SecureStorageService.kt rename to matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/securestorage/SecureStorageService.kt index 6b75c94cb2..e217611d96 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/securestorage/SecureStorageService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/securestorage/SecureStorageService.kt @@ -14,7 +14,7 @@ * limitations under the License. */ -package org.matrix.android.sdk.api.session.securestorage +package org.matrix.android.sdk.api.securestorage import java.io.InputStream import java.io.OutputStream 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 2f1ae8cd87..1b01239de5 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 @@ -17,8 +17,7 @@ package org.matrix.android.sdk.api.session import androidx.annotation.MainThread -import androidx.lifecycle.LiveData -import kotlinx.coroutines.flow.SharedFlow +import io.realm.RealmConfiguration import okhttp3.OkHttpClient import org.matrix.android.sdk.api.MatrixCoroutineDispatchers import org.matrix.android.sdk.api.auth.data.SessionParams @@ -37,7 +36,6 @@ import org.matrix.android.sdk.api.session.file.FileService import org.matrix.android.sdk.api.session.group.GroupService import org.matrix.android.sdk.api.session.homeserver.HomeServerCapabilitiesService import org.matrix.android.sdk.api.session.identity.IdentityService -import org.matrix.android.sdk.api.session.initsync.SyncStatusService import org.matrix.android.sdk.api.session.integrationmanager.IntegrationManagerService import org.matrix.android.sdk.api.session.media.MediaService import org.matrix.android.sdk.api.session.openid.OpenIdService @@ -49,14 +47,12 @@ 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 -import org.matrix.android.sdk.api.session.securestorage.SecureStorageService import org.matrix.android.sdk.api.session.securestorage.SharedSecretStorageService import org.matrix.android.sdk.api.session.signout.SignOutService import org.matrix.android.sdk.api.session.space.SpaceService import org.matrix.android.sdk.api.session.statistics.StatisticsListener import org.matrix.android.sdk.api.session.sync.FilterService -import org.matrix.android.sdk.api.session.sync.SyncState -import org.matrix.android.sdk.api.session.sync.model.SyncResponse +import org.matrix.android.sdk.api.session.sync.SyncService import org.matrix.android.sdk.api.session.terms.TermsService import org.matrix.android.sdk.api.session.thirdparty.ThirdPartyService import org.matrix.android.sdk.api.session.typing.TypingUsersTracker @@ -98,59 +94,11 @@ interface Session { @MainThread fun open() - /** - * Requires a one time background sync. - */ - fun requireBackgroundSync() - - /** - * Launches infinite self rescheduling background syncs via the WorkManager. - * - * While dozing, syncs will only occur during maintenance windows. - * For reliability it's recommended to also start a long running foreground service - * along with disabling battery optimizations. - */ - fun startAutomaticBackgroundSync(timeOutInSeconds: Long, repeatDelayInSeconds: Long) - - fun stopAnyBackgroundSync() - - /** - * This method start the sync thread. - */ - fun startSync(fromForeground: Boolean) - - /** - * This method stop the sync thread. - */ - fun stopSync() - /** * Clear cache of the session. */ suspend fun clearCache() - /** - * This method allows to listen the sync state. - * @return a [LiveData] of [SyncState]. - */ - fun getSyncStateLive(): LiveData - - /** - * This method returns the current sync state. - * @return the current [SyncState]. - */ - fun getSyncState(): SyncState - - /** - * This method returns a flow of SyncResponse. New value will be pushed through the sync thread. - */ - fun syncFlow(): SharedFlow - - /** - * This methods return true if an initial sync has been processed. - */ - fun hasAlreadySynced(): Boolean - /** * This method allow to close a session. It does stop some services. */ @@ -247,14 +195,9 @@ interface Session { fun termsService(): TermsService /** - * Returns the SyncStatusService associated with the session. + * Returns the SyncService associated with the session. */ - fun syncStatusService(): SyncStatusService - - /** - * Returns the SecureStorageService associated with the session. - */ - fun secureStorageService(): SecureStorageService + fun syncService(): SyncService /** * Returns the ProfileService associated with the session. @@ -386,7 +329,12 @@ interface Session { fun getUiaSsoFallbackUrl(authenticationSessionId: String): String /** - * Maintenance API, allows to print outs info on DB size to logcat. + * Debug API, will print out info on DB size to logcat. */ fun logDbUsageInfo() + + /** + * Debug API, return the list of all RealmConfiguration used by this session. + */ + fun getRealmConfigurations(): List } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/account/AccountService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/account/AccountService.kt index 1f28dbd8af..094c66f6f7 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/account/AccountService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/account/AccountService.kt @@ -24,11 +24,13 @@ import org.matrix.android.sdk.api.auth.UserInteractiveAuthInterceptor interface AccountService { /** * Ask the homeserver to change the password. + * * @param password Current password. * @param newPassword New password + * @param logoutAllDevices defaults to true, all devices will be logged out. False values will only be taken into account + * if [org.matrix.android.sdk.api.session.homeserver.HomeServerCapabilities.canControlLogoutDevices] is true. */ - suspend fun changePassword(password: String, - newPassword: String) + suspend fun changePassword(password: String, newPassword: String, logoutAllDevices: Boolean = true) /** * Deactivate the account. @@ -46,6 +48,8 @@ interface AccountService { * an incomplete view of conversations * @param userInteractiveAuthInterceptor see [UserInteractiveAuthInterceptor] */ - suspend fun deactivateAccount(eraseAllData: Boolean, - userInteractiveAuthInterceptor: UserInteractiveAuthInterceptor) + suspend fun deactivateAccount( + eraseAllData: Boolean, + userInteractiveAuthInterceptor: UserInteractiveAuthInterceptor + ) } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/call/MxCall.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/call/MxCall.kt index e13f7310e0..c87ac3c821 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/call/MxCall.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/call/MxCall.kt @@ -91,10 +91,12 @@ interface MxCall : MxCallDetail { * Send a m.call.replaces event to initiate call transfer. * See [org.matrix.android.sdk.api.session.room.model.call.CallReplacesContent] for documentation about the parameters */ - suspend fun transfer(targetUserId: String, - targetRoomId: String?, - createCallId: String?, - awaitCallId: String?) + suspend fun transfer( + targetUserId: String, + targetRoomId: String?, + createCallId: String?, + awaitCallId: String? + ) fun addListener(listener: StateListener) fun removeListener(listener: StateListener) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/CryptoService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/CryptoService.kt index 35f3ab3162..638da11804 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/CryptoService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/CryptoService.kt @@ -88,9 +88,11 @@ interface CryptoService { fun getDeviceTrackingStatus(userId: String): Int - suspend fun importRoomKeys(roomKeysAsArray: ByteArray, - password: String, - progressListener: ProgressListener?): ImportRoomKeysResult + suspend fun importRoomKeys( + roomKeysAsArray: ByteArray, + password: String, + progressListener: ProgressListener? + ): ImportRoomKeysResult suspend fun exportRoomKeys(password: String): ByteArray @@ -119,10 +121,12 @@ interface CryptoService { fun isRoomEncrypted(roomId: String): Boolean // TODO This could be removed from this interface - fun encryptEventContent(eventContent: Content, - eventType: String, - roomId: String, - callback: MatrixCallback) + fun encryptEventContent( + eventContent: Content, + eventType: String, + roomId: String, + callback: MatrixCallback + ) fun discardOutboundSession(roomId: String) @@ -167,8 +171,6 @@ interface CryptoService { fun getSharedWithInfo(roomId: String?, sessionId: String): MXUsersDevicesMap fun getWithHeldMegolmSession(roomId: String, sessionId: String): RoomKeyWithHeldContent? - fun logDbUsageInfo() - /** * Perform any background tasks that can be done before a message is ready to * send, in order to speed up sending of the message. diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/MXCryptoError.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/MXCryptoError.kt index 0b5bbe3bbd..94ee7ba403 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/MXCryptoError.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/MXCryptoError.kt @@ -25,12 +25,14 @@ import org.matrix.olm.OlmException */ sealed class MXCryptoError : Throwable() { - data class Base(val errorType: ErrorType, - val technicalMessage: String, - /** - * Describe the error with more details. - */ - val detailedErrorDescription: String? = null) : MXCryptoError() + data class Base( + val errorType: ErrorType, + val technicalMessage: String, + /** + * Describe the error with more details. + */ + val detailedErrorDescription: String? = null + ) : MXCryptoError() data class OlmError(val olmException: OlmException) : MXCryptoError() diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/OutgoingKeyRequest.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/OutgoingKeyRequest.kt index 855f17a34f..7202be7a21 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/OutgoingKeyRequest.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/OutgoingKeyRequest.kt @@ -26,7 +26,7 @@ data class RequestReply( ) sealed class RequestResult { - data class Success(val chainIndex: Int) : RequestResult() + data class Success(val chainIndex: Int) : RequestResult() data class Failure(val code: WithHeldCode) : RequestResult() } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/crosssigning/CrossSigningService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/crosssigning/CrossSigningService.kt index 5439389096..69f314f76f 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/crosssigning/CrossSigningService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/crosssigning/CrossSigningService.kt @@ -37,14 +37,18 @@ interface CrossSigningService { * Initialize cross signing for this user. * Users needs to enter credentials */ - fun initializeCrossSigning(uiaInterceptor: UserInteractiveAuthInterceptor?, - callback: MatrixCallback) + fun initializeCrossSigning( + uiaInterceptor: UserInteractiveAuthInterceptor?, + callback: MatrixCallback + ) fun isCrossSigningInitialized(): Boolean = getMyCrossSigningKeys() != null - fun checkTrustFromPrivateKeys(masterKeyPrivateKey: String?, - uskKeyPrivateKey: String?, - sskPrivateKey: String?): UserTrustResult + fun checkTrustFromPrivateKeys( + masterKeyPrivateKey: String?, + uskKeyPrivateKey: String?, + sskPrivateKey: String? + ): UserTrustResult fun getUserCrossSigningKeys(otherUserId: String): MXCrossSigningInfo? @@ -60,20 +64,26 @@ interface CrossSigningService { fun allPrivateKeysKnown(): Boolean - fun trustUser(otherUserId: String, - callback: MatrixCallback) + fun trustUser( + otherUserId: String, + callback: MatrixCallback + ) fun markMyMasterKeyAsTrusted() /** * Sign one of your devices and upload the signature. */ - fun trustDevice(deviceId: String, - callback: MatrixCallback) + fun trustDevice( + deviceId: String, + callback: MatrixCallback + ) - fun checkDeviceTrust(otherUserId: String, - otherDeviceId: String, - locallyTrusted: Boolean?): DeviceTrustResult + fun checkDeviceTrust( + otherUserId: String, + otherDeviceId: String, + locallyTrusted: Boolean? + ): DeviceTrustResult // FIXME Those method do not have to be in the service fun onSecretMSKGossip(mskPrivateKey: String) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/keysbackup/KeysBackupLastVersionResult.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/keysbackup/KeysBackupLastVersionResult.kt index a7e985cea9..92510bb52e 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/keysbackup/KeysBackupLastVersionResult.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/keysbackup/KeysBackupLastVersionResult.kt @@ -24,5 +24,5 @@ sealed interface KeysBackupLastVersionResult { fun KeysBackupLastVersionResult.toKeysVersionResult(): KeysVersionResult? = when (this) { is KeysBackupLastVersionResult.KeysBackup -> keysVersionResult - KeysBackupLastVersionResult.NoKeysBackup -> null + KeysBackupLastVersionResult.NoKeysBackup -> null } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/keysbackup/KeysBackupService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/keysbackup/KeysBackupService.kt index 9029c7f8a3..8745003f9f 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/keysbackup/KeysBackupService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/keysbackup/KeysBackupService.kt @@ -36,8 +36,10 @@ interface KeysBackupService { * @param keysBackupCreationInfo the info object from [prepareKeysBackupVersion]. * @param callback Asynchronous callback */ - fun createKeysBackupVersion(keysBackupCreationInfo: MegolmBackupCreationInfo, - callback: MatrixCallback) + fun createKeysBackupVersion( + keysBackupCreationInfo: MegolmBackupCreationInfo, + callback: MatrixCallback + ) /** * Facility method to get the total number of locally stored keys. @@ -55,8 +57,10 @@ interface KeysBackupService { * @param progressListener the callback to follow the progress * @param callback the main callback */ - fun backupAllGroupSessions(progressListener: ProgressListener?, - callback: MatrixCallback?) + fun backupAllGroupSessions( + progressListener: ProgressListener?, + callback: MatrixCallback? + ) /** * Check trust on a key backup version. @@ -64,8 +68,10 @@ interface KeysBackupService { * @param keysBackupVersion the backup version to check. * @param callback block called when the operations completes. */ - fun getKeysBackupTrust(keysBackupVersion: KeysVersionResult, - callback: MatrixCallback) + fun getKeysBackupTrust( + keysBackupVersion: KeysVersionResult, + callback: MatrixCallback + ) /** * Return the current progress of the backup. @@ -79,8 +85,10 @@ interface KeysBackupService { * @param version the backup version * @param callback */ - fun getVersion(version: String, - callback: MatrixCallback) + fun getVersion( + version: String, + callback: MatrixCallback + ) /** * This method fetches the last backup version on the server, then compare to the currently backup version use. @@ -114,9 +122,11 @@ interface KeysBackupService { * @param progressListener a progress listener, as generating private key from password may take a while * @param callback Asynchronous callback */ - fun prepareKeysBackupVersion(password: String?, - progressListener: ProgressListener?, - callback: MatrixCallback) + fun prepareKeysBackupVersion( + password: String?, + progressListener: ProgressListener?, + callback: MatrixCallback + ) /** * Delete a keys backup version. It will delete all backed up keys on the server, and the backup itself. @@ -125,8 +135,10 @@ interface KeysBackupService { * @param version the backup version to delete. * @param callback Asynchronous callback */ - fun deleteBackup(version: String, - callback: MatrixCallback?) + fun deleteBackup( + version: String, + callback: MatrixCallback? + ) /** * Ask if the backup on the server contains keys that we may do not have locally. @@ -142,9 +154,11 @@ interface KeysBackupService { * @param trust the trust to set to the keys backup. * @param callback block called when the operations completes. */ - fun trustKeysBackupVersion(keysBackupVersion: KeysVersionResult, - trust: Boolean, - callback: MatrixCallback) + fun trustKeysBackupVersion( + keysBackupVersion: KeysVersionResult, + trust: Boolean, + callback: MatrixCallback + ) /** * Set trust on a keys backup version. @@ -153,9 +167,11 @@ interface KeysBackupService { * @param recoveryKey the recovery key to challenge with the key backup public key. * @param callback block called when the operations completes. */ - fun trustKeysBackupVersionWithRecoveryKey(keysBackupVersion: KeysVersionResult, - recoveryKey: String, - callback: MatrixCallback) + fun trustKeysBackupVersionWithRecoveryKey( + keysBackupVersion: KeysVersionResult, + recoveryKey: String, + callback: MatrixCallback + ) /** * Set trust on a keys backup version. @@ -164,11 +180,11 @@ interface KeysBackupService { * @param password the pass phrase to challenge with the keyBackupVersion public key. * @param callback block called when the operations completes. */ - fun trustKeysBackupVersionWithPassphrase(keysBackupVersion: KeysVersionResult, - password: String, - callback: MatrixCallback) - - fun onSecretKeyGossip(secret: String) + fun trustKeysBackupVersionWithPassphrase( + keysBackupVersion: KeysVersionResult, + password: String, + callback: MatrixCallback + ) /** * Restore a backup with a recovery key from a given backup version stored on the homeserver. @@ -180,11 +196,14 @@ interface KeysBackupService { * @param stepProgressListener the step progress listener * @param callback Callback. It provides the number of found keys and the number of successfully imported keys. */ - fun restoreKeysWithRecoveryKey(keysVersionResult: KeysVersionResult, - recoveryKey: String, roomId: String?, - sessionId: String?, - stepProgressListener: StepProgressListener?, - callback: MatrixCallback) + fun restoreKeysWithRecoveryKey( + keysVersionResult: KeysVersionResult, + recoveryKey: String, + roomId: String?, + sessionId: String?, + stepProgressListener: StepProgressListener?, + callback: MatrixCallback + ) /** * Restore a backup with a password from a given backup version stored on the homeserver. @@ -196,18 +215,23 @@ interface KeysBackupService { * @param stepProgressListener the step progress listener * @param callback Callback. It provides the number of found keys and the number of successfully imported keys. */ - fun restoreKeyBackupWithPassword(keysBackupVersion: KeysVersionResult, - password: String, - roomId: String?, - sessionId: String?, - stepProgressListener: StepProgressListener?, - callback: MatrixCallback) + fun restoreKeyBackupWithPassword( + keysBackupVersion: KeysVersionResult, + password: String, + roomId: String?, + sessionId: String?, + stepProgressListener: StepProgressListener?, + callback: MatrixCallback + ) val keysBackupVersion: KeysVersionResult? + val currentBackupVersion: String? - val isEnabled: Boolean - val isStucked: Boolean - val state: KeysBackupState + get() = keysBackupVersion?.version + + fun isEnabled(): Boolean + fun isStuck(): Boolean + fun getState(): KeysBackupState // For gossiping fun saveBackupRecoveryKey(recoveryKey: String?, version: String?) @@ -215,8 +239,10 @@ interface KeysBackupService { fun isValidRecoveryKeyForCurrentVersion(recoveryKey: String, callback: MatrixCallback) - fun computePrivateKey(passphrase: String, - privateKeySalt: String, - privateKeyIterations: Int, - progressListener: ProgressListener): ByteArray + fun computePrivateKey( + passphrase: String, + privateKeySalt: String, + privateKeyIterations: Int, + progressListener: ProgressListener + ): ByteArray } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/keysbackup/KeysBackupState.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/keysbackup/KeysBackupState.kt index a4cc133398..a867d573de 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/keysbackup/KeysBackupState.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/keysbackup/KeysBackupState.kt @@ -51,33 +51,51 @@ package org.matrix.android.sdk.api.session.crypto.keysbackup * */ enum class KeysBackupState { - // Need to check the current backup version on the homeserver + /** + * Need to check the current backup version on the homeserver. + */ Unknown, - // Checking if backup is enabled on homeserver + /** + * Checking if backup is enabled on homeserver. + */ CheckingBackUpOnHomeserver, - // Backup has been stopped because a new backup version has been detected on the homeserver + /** + * Backup has been stopped because a new backup version has been detected on the homeserver. + */ WrongBackUpVersion, - // Backup from this device is not enabled + /** + * Backup from this device is not enabled. + */ Disabled, - // There is a backup available on the homeserver but it is not trusted. - // It is not trusted because the signature is invalid or the device that created it is not verified - // Use [KeysBackup.getKeysBackupTrust()] to get trust details. - // Consequently, the backup from this device is not enabled. + /** + * There is a backup available on the homeserver but it is not trusted. + * It is not trusted because the signature is invalid or the device that created it is not verified. + * Use [KeysBackup.getKeysBackupTrust()] to get trust details. + * Consequently, the backup from this device is not enabled. + */ NotTrusted, - // Backup is being enabled: the backup version is being created on the homeserver + /** + * Backup is being enabled: the backup version is being created on the homeserver. + */ Enabling, - // Backup is enabled and ready to send backup to the homeserver + /** + * Backup is enabled and ready to send backup to the homeserver. + */ ReadyToBackUp, - // e2e keys are going to be sent to the homeserver + /** + * e2e keys are going to be sent to the homeserver. + */ WillBackUp, - // e2e keys are being sent to the homeserver + /** + * e2e keys are being sent to the homeserver. + */ BackingUp } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/keysbackup/KeysBackupVersionTrustSignature.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/keysbackup/KeysBackupVersionTrustSignature.kt index 7127c8d3f4..afbf45ac70 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/keysbackup/KeysBackupVersionTrustSignature.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/keysbackup/KeysBackupVersionTrustSignature.kt @@ -40,7 +40,8 @@ sealed class KeysBackupVersionTrustSignature { /** * Flag to indicate the signature from this device is valid. */ - val valid: Boolean) : KeysBackupVersionTrustSignature() + val valid: Boolean + ) : KeysBackupVersionTrustSignature() data class UserSignature( val keyId: String?, diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/model/RoomEncryptionTrustLevel.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/model/RoomEncryptionTrustLevel.kt index 68c7496d58..78724819a3 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/model/RoomEncryptionTrustLevel.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/model/RoomEncryptionTrustLevel.kt @@ -20,15 +20,23 @@ package org.matrix.android.sdk.api.session.crypto.model * RoomEncryptionTrustLevel represents the trust level in an encrypted room. */ enum class RoomEncryptionTrustLevel { - // No one in the room has been verified -> Black shield + /** + * No one in the room has been verified -> Black shield. + */ Default, - // There are one or more device un-verified -> the app should display a red shield + /** + * There are one or more device un-verified -> the app should display a red shield. + */ Warning, - // All devices in the room are verified -> the app should display a green shield + /** + * All devices in the room are verified -> the app should display a green shield. + */ Trusted, - // e2e is active but with an unsupported algorithm + /** + * e2e is active but with an unsupported algorithm. + */ E2EWithUnsupportedAlgorithm } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/verification/EmojiRepresentation.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/verification/EmojiRepresentation.kt index 2c1bf9ff4d..5402471e46 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/verification/EmojiRepresentation.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/verification/EmojiRepresentation.kt @@ -19,7 +19,8 @@ package org.matrix.android.sdk.api.session.crypto.verification import androidx.annotation.DrawableRes import androidx.annotation.StringRes -data class EmojiRepresentation(val emoji: String, - @StringRes val nameResId: Int, - @DrawableRes val drawableRes: Int? = null +data class EmojiRepresentation( + val emoji: String, + @StringRes val nameResId: Int, + @DrawableRes val drawableRes: Int? = null ) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/verification/VerificationMethod.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/verification/VerificationMethod.kt index f2de2c4b47..0ab47a2ecd 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/verification/VerificationMethod.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/verification/VerificationMethod.kt @@ -20,12 +20,18 @@ package org.matrix.android.sdk.api.session.crypto.verification * Verification methods. */ enum class VerificationMethod { - // Use it when your application supports the SAS verification method + /** + * Use it when your application supports the SAS verification method. + */ SAS, - // Use it if your application is able to display QR codes + /** + * Use it if your application is able to display QR codes. + */ QR_CODE_SHOW, - // Use it if your application is able to scan QR codes + /** + * Use it if your application is able to scan QR codes. + */ QR_CODE_SCAN } 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 321ec73094..ee93f14992 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 @@ -46,54 +46,68 @@ interface VerificationService { fun getExistingVerificationRequestInRoom(roomId: String, tid: String?): PendingVerificationRequest? - fun beginKeyVerification(method: VerificationMethod, - otherUserId: String, - otherDeviceId: String, - transactionId: String?): String? + fun beginKeyVerification( + method: VerificationMethod, + otherUserId: String, + otherDeviceId: String, + transactionId: String? + ): String? /** * Request key verification with another user via room events (instead of the to-device API). */ - fun requestKeyVerificationInDMs(methods: List, - otherUserId: String, - roomId: String, - localId: String? = LocalEcho.createLocalEchoId()): PendingVerificationRequest + fun requestKeyVerificationInDMs( + methods: List, + otherUserId: String, + roomId: String, + localId: String? = LocalEcho.createLocalEchoId() + ): PendingVerificationRequest fun cancelVerificationRequest(request: PendingVerificationRequest) /** * Request a key verification from another user using toDevice events. */ - fun requestKeyVerification(methods: List, - otherUserId: String, - otherDevices: List?): PendingVerificationRequest + fun requestKeyVerification( + methods: List, + otherUserId: String, + otherDevices: List? + ): PendingVerificationRequest - fun declineVerificationRequestInDMs(otherUserId: String, - transactionId: String, - roomId: String) + fun declineVerificationRequestInDMs( + otherUserId: String, + transactionId: String, + roomId: String + ) // Only SAS method is supported for the moment // TODO Parameter otherDeviceId should be removed in this case - fun beginKeyVerificationInDMs(method: VerificationMethod, - transactionId: String, - roomId: String, - otherUserId: String, - otherDeviceId: String): String + fun beginKeyVerificationInDMs( + method: VerificationMethod, + transactionId: String, + roomId: String, + otherUserId: String, + otherDeviceId: String + ): String /** * Returns false if the request is unknown. */ - fun readyPendingVerificationInDMs(methods: List, - otherUserId: String, - roomId: String, - transactionId: String): Boolean + fun readyPendingVerificationInDMs( + methods: List, + otherUserId: String, + roomId: String, + transactionId: String + ): Boolean /** * Returns false if the request is unknown. */ - fun readyPendingVerification(methods: List, - otherUserId: String, - transactionId: String): Boolean + fun readyPendingVerification( + methods: List, + otherUserId: String, + transactionId: String + ): Boolean interface Listener { /** diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/verification/VerificationTxState.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/verification/VerificationTxState.kt index 39de2cc712..30e4c66937 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/verification/VerificationTxState.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/verification/VerificationTxState.kt @@ -17,10 +17,14 @@ package org.matrix.android.sdk.api.session.crypto.verification sealed class VerificationTxState { - // Uninitialized state + /** + * Uninitialized state. + */ object None : VerificationTxState() - // Specific for SAS + /** + * Specific for SAS. + */ abstract class VerificationSasTxState : VerificationTxState() object SendingStart : VerificationSasTxState() @@ -38,18 +42,26 @@ sealed class VerificationTxState { object MacSent : VerificationSasTxState() object Verifying : VerificationSasTxState() - // Specific for QR code + /** + * Specific for QR code. + */ abstract class VerificationQrTxState : VerificationTxState() - // Will be used to ask the user if the other user has correctly scanned + /** + * Will be used to ask the user if the other user has correctly scanned. + */ object QrScannedByOther : VerificationQrTxState() object WaitingOtherReciprocateConfirm : VerificationQrTxState() - // Terminal states + /** + * Terminal states. + */ abstract class TerminalTxState : VerificationTxState() object Verified : TerminalTxState() - // Cancelled by me or by other + /** + * Cancelled by me or by other. + */ data class Cancelled(val cancelCode: CancelCode, val byMe: Boolean) : TerminalTxState() } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/events/EventService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/events/EventService.kt index 297f277497..7f275bf952 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/events/EventService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/events/EventService.kt @@ -24,6 +24,8 @@ interface EventService { * Ask the homeserver for an event content. The SDK will try to decrypt it if it is possible * The result will not be stored into cache */ - suspend fun getEvent(roomId: String, - eventId: String): Event + suspend fun getEvent( + roomId: String, + eventId: String + ): Event } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/events/model/Event.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/events/model/Event.kt index 7124d8a1a3..7f9ab4c6dd 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/events/model/Event.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/events/model/Event.kt @@ -212,13 +212,13 @@ data class Event( return when { isReplyRenderedInThread() || isQuote() -> ContentUtils.extractUsefulTextFromReply(text) - isFileMessage() -> "sent a file." - isAudioMessage() -> "sent an audio file." - isImageMessage() -> "sent an image." - isVideoMessage() -> "sent a video." - isSticker() -> "sent a sticker" - isPoll() -> getPollQuestion() ?: "created a poll." - else -> text + isFileMessage() -> "sent a file." + isAudioMessage() -> "sent an audio file." + isImageMessage() -> "sent an image." + isVideoMessage() -> "sent a video." + isSticker() -> "sent a sticker" + isPoll() -> getPollQuestion() ?: "created a poll." + else -> text } } @@ -318,35 +318,35 @@ fun Event.isTextMessage(): Boolean { MessageType.MSGTYPE_TEXT, MessageType.MSGTYPE_EMOTE, MessageType.MSGTYPE_NOTICE -> true - else -> false + else -> false } } fun Event.isImageMessage(): Boolean { return when (getMsgType()) { MessageType.MSGTYPE_IMAGE -> true - else -> false + else -> false } } fun Event.isVideoMessage(): Boolean { return when (getMsgType()) { MessageType.MSGTYPE_VIDEO -> true - else -> false + else -> false } } fun Event.isAudioMessage(): Boolean { return when (getMsgType()) { MessageType.MSGTYPE_AUDIO -> true - else -> false + else -> false } } fun Event.isFileMessage(): Boolean { return when (getMsgType()) { MessageType.MSGTYPE_FILE -> true - else -> false + else -> false } } @@ -356,14 +356,14 @@ fun Event.isAttachmentMessage(): Boolean { MessageType.MSGTYPE_AUDIO, MessageType.MSGTYPE_VIDEO, MessageType.MSGTYPE_FILE -> true - else -> false + else -> false } } fun Event.isLocationMessage(): Boolean { return when (getMsgType()) { MessageType.MSGTYPE_LOCATION -> true - else -> false + else -> false } } @@ -378,9 +378,9 @@ fun Event.getRelationContent(): RelationDefaultContent? { content.toModel()?.relatesTo ?: run { // Special cases when there is only a local msgtype for some event types when (getClearType()) { - EventType.STICKER -> getClearContent().toModel()?.relatesTo + EventType.STICKER -> getClearContent().toModel()?.relatesTo in EventType.BEACON_LOCATION_DATA -> getClearContent().toModel()?.relatesTo - else -> null + else -> null } } } @@ -427,3 +427,6 @@ fun Event.getPollContent(): MessagePollContent? { fun Event.supportsNotification() = this.getClearType() in EventType.MESSAGE + EventType.POLL_START + EventType.STATE_ROOM_BEACON_INFO + +fun Event.isContentReportable() = + this.getClearType() in EventType.MESSAGE + EventType.STATE_ROOM_BEACON_INFO diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/events/model/content/RoomKeyWithHeldContent.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/events/model/content/RoomKeyWithHeldContent.kt index d58c3614a7..fb8b65c4f2 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/events/model/content/RoomKeyWithHeldContent.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/events/model/content/RoomKeyWithHeldContent.kt @@ -98,12 +98,12 @@ enum class WithHeldCode(val value: String) { companion object { fun fromCode(code: String?): WithHeldCode? { return when (code) { - BLACKLISTED.value -> BLACKLISTED - UNVERIFIED.value -> UNVERIFIED + BLACKLISTED.value -> BLACKLISTED + UNVERIFIED.value -> UNVERIFIED UNAUTHORISED.value -> UNAUTHORISED - UNAVAILABLE.value -> UNAVAILABLE - NO_OLM.value -> NO_OLM - else -> null + UNAVAILABLE.value -> UNAVAILABLE + NO_OLM.value -> NO_OLM + else -> null } } } 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 a7c81136e3..ca6c889cb8 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 @@ -44,10 +44,12 @@ interface FileService { * Download a file if necessary and ensure that if the file is encrypted, the file is decrypted. * 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 + suspend fun downloadFile( + fileName: String, + mimeType: String?, + url: String?, + elementToDecrypt: ElementToDecrypt? + ): File suspend fun downloadFile(messageContent: MessageWithAttachmentContent): File = downloadFile( @@ -57,10 +59,11 @@ interface FileService { elementToDecrypt = messageContent.encryptedFileInfo?.toElementToDecrypt() ) - fun isFileInCache(mxcUrl: String?, - fileName: String, - mimeType: String?, - elementToDecrypt: ElementToDecrypt? + fun isFileInCache( + mxcUrl: String?, + fileName: String, + mimeType: String?, + elementToDecrypt: ElementToDecrypt? ): Boolean fun isFileInCache(messageContent: MessageWithAttachmentContent) = @@ -75,10 +78,12 @@ interface FileService { * Use this URI and pass it to intent using flag Intent.FLAG_GRANT_READ_URI_PERMISSION * (if not other app won't be able to access it). */ - fun getTemporarySharableURI(mxcUrl: String?, - fileName: String, - mimeType: String?, - elementToDecrypt: ElementToDecrypt?): Uri? + fun getTemporarySharableURI( + mxcUrl: String?, + fileName: String, + mimeType: String?, + elementToDecrypt: ElementToDecrypt? + ): Uri? fun getTemporarySharableURI(messageContent: MessageWithAttachmentContent): Uri? = getTemporarySharableURI( @@ -92,10 +97,12 @@ interface FileService { * Get information on the given file. * Mimetype should be the same one as passed to downloadFile (limitation for now) */ - fun fileState(mxcUrl: String?, - fileName: String, - mimeType: String?, - elementToDecrypt: ElementToDecrypt?): FileState + fun fileState( + mxcUrl: String?, + fileName: String, + mimeType: String?, + elementToDecrypt: ElementToDecrypt? + ): FileState fun fileState(messageContent: MessageWithAttachmentContent): FileState = fileState( diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/homeserver/HomeServerCapabilities.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/homeserver/HomeServerCapabilities.kt index 5b06fdacae..b5d6d891e4 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/homeserver/HomeServerCapabilities.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/homeserver/HomeServerCapabilities.kt @@ -54,7 +54,12 @@ data class HomeServerCapabilities( /** * True if the home server support threading. */ - val canUseThreading: Boolean = false + val canUseThreading: Boolean = false, + + /** + * True if the home server supports controlling the logout of all devices when changing password. + */ + val canControlLogoutDevices: Boolean = false ) { enum class RoomCapabilitySupport { @@ -81,13 +86,13 @@ data class HomeServerCapabilities( val versionCap = roomVersions.supportedVersion.firstOrNull { it.version == preferred } return when { - versionCap == null -> { + versionCap == null -> { RoomCapabilitySupport.UNKNOWN } versionCap.status == RoomVersionStatus.STABLE -> { RoomCapabilitySupport.SUPPORTED } - else -> { + else -> { RoomCapabilitySupport.SUPPORTED_UNSTABLE } } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/identity/ThreePid.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/identity/ThreePid.kt index 42d777849b..6bcf576824 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/identity/ThreePid.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/identity/ThreePid.kt @@ -27,7 +27,7 @@ sealed class ThreePid(open val value: String) { internal fun ThreePid.toMedium(): String { return when (this) { - is ThreePid.Email -> ThirdPartyIdentifier.MEDIUM_EMAIL + is ThreePid.Email -> ThirdPartyIdentifier.MEDIUM_EMAIL is ThreePid.Msisdn -> ThirdPartyIdentifier.MEDIUM_MSISDN } } 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 deleted file mode 100644 index 7006e11751..0000000000 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/initsync/SyncStatusService.kt +++ /dev/null @@ -1,50 +0,0 @@ -/* - * Copyright 2020 The Matrix.org Foundation C.I.C. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.matrix.android.sdk.api.session.initsync - -import androidx.lifecycle.LiveData - -interface SyncStatusService { - - fun getSyncStatusLive(): LiveData - - sealed class Status { - /** - * For initial sync. - */ - abstract class InitialSyncStatus : Status() - - object Idle : InitialSyncStatus() - data class InitialSyncProgressing( - val initSyncStep: InitSyncStep, - val percentProgress: Int = 0 - ) : InitialSyncStatus() - - /** - * For incremental sync. - */ - abstract class IncrementalSyncStatus : Status() - - object IncrementalSyncIdle : IncrementalSyncStatus() - data class IncrementalSyncParsing( - 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/MatrixPermalinkSpan.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/permalinks/MatrixPermalinkSpan.kt index 48b30dfa21..9c71c081be 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/permalinks/MatrixPermalinkSpan.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/permalinks/MatrixPermalinkSpan.kt @@ -25,8 +25,10 @@ import org.matrix.android.sdk.api.session.permalinks.MatrixPermalinkSpan.Callbac * @property url the permalink url tied to the span * @property callback the callback to use. */ -class MatrixPermalinkSpan(private val url: String, - private val callback: Callback? = null) : ClickableSpan() { +class MatrixPermalinkSpan( + private val url: String, + private val callback: Callback? = null +) : ClickableSpan() { interface Callback { fun onUrlClicked(url: String) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/permalinks/MatrixToConverter.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/permalinks/MatrixToConverter.kt index a904e89681..c418b59df4 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/permalinks/MatrixToConverter.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/permalinks/MatrixToConverter.kt @@ -38,12 +38,12 @@ object MatrixToConverter { // URL is already a matrix.to uriString.startsWith(PermalinkService.MATRIX_TO_URL_BASE) -> uri // Web or client url - SUPPORTED_PATHS.any { it in uriString } -> { + SUPPORTED_PATHS.any { it in uriString } -> { val path = SUPPORTED_PATHS.first { it in uriString } Uri.parse(PermalinkService.MATRIX_TO_URL_BASE + uriString.substringAfter(path)) } // URL is not supported - else -> null + else -> null } } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/permalinks/PermalinkParser.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/permalinks/PermalinkParser.kt index 9d078dc4b2..0168b7ac3a 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/permalinks/PermalinkParser.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/permalinks/PermalinkParser.kt @@ -67,10 +67,10 @@ object PermalinkParser { val identifier = params.getOrNull(0) val extraParameter = params.getOrNull(1) return when { - identifier.isNullOrEmpty() -> PermalinkData.FallbackLink(uri) - MatrixPatterns.isUserId(identifier) -> PermalinkData.UserLink(userId = identifier) - MatrixPatterns.isGroupId(identifier) -> PermalinkData.GroupLink(groupId = identifier) - MatrixPatterns.isRoomId(identifier) -> { + identifier.isNullOrEmpty() -> PermalinkData.FallbackLink(uri) + MatrixPatterns.isUserId(identifier) -> PermalinkData.UserLink(userId = identifier) + MatrixPatterns.isGroupId(identifier) -> PermalinkData.GroupLink(groupId = identifier) + MatrixPatterns.isRoomId(identifier) -> { handleRoomIdCase(fragment, identifier, matrixToUri, extraParameter, viaQueryParameters) } MatrixPatterns.isRoomAlias(identifier) -> { @@ -81,7 +81,7 @@ object PermalinkParser { viaParameters = viaQueryParameters ) } - else -> PermalinkData.FallbackLink(uri) + else -> PermalinkData.FallbackLink(uri) } } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/profile/ProfileService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/profile/ProfileService.kt index 095f2ef7c2..4c00c76459 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/profile/ProfileService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/profile/ProfileService.kt @@ -107,8 +107,10 @@ interface ProfileService { /** * Finalize adding a 3Pids. Call this method once the user has validated that he owns the ThreePid. */ - suspend fun finalizeAddingThreePid(threePid: ThreePid, - userInteractiveAuthInterceptor: UserInteractiveAuthInterceptor) + suspend fun finalizeAddingThreePid( + threePid: ThreePid, + userInteractiveAuthInterceptor: UserInteractiveAuthInterceptor + ) /** * Cancel adding a threepid. It will remove locally stored data about this ThreePid. diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/pushers/HttpPusher.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/pushers/HttpPusher.kt new file mode 100644 index 0000000000..1ae23e2b70 --- /dev/null +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/pushers/HttpPusher.kt @@ -0,0 +1,73 @@ +/* + * 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.pushers + +data class HttpPusher( + /** + * This is a unique identifier for this pusher. The value you should use for + * this is the routing or destination address information for the notification, + * for example, the APNS token for APNS or the Registration ID for GCM. If your + * notification client has no such concept, use any unique identifier. Max length, 512 chars. + */ + val pushkey: String, + + /** + * The application id + * This is a reverse-DNS style identifier for the application. It is recommended + * that this end with the platform, such that different platform versions get + * different app identifiers. Max length, 64 chars. + */ + val appId: String, + + /** + * This string determines which set of device specific rules this pusher executes. + */ + val profileTag: String, + + /** + * The preferred language for receiving notifications (e.g. "en" or "en-US"). + */ + val lang: String, + + /** + * A human readable string that will allow the user to identify what application owns this pusher. + */ + val appDisplayName: String, + + /** + * A human readable string that will allow the user to identify what device owns this pusher. + */ + val deviceDisplayName: String, + + /** + * The URL to use to send notifications to. MUST be an HTTPS URL with a path of /_matrix/push/v1/notify. + */ + val url: String, + + /** + * If true, the homeserver should add another pusher with the given pushkey and App ID in addition + * to any others with different user IDs. Otherwise, the homeserver must remove any other pushers + * with the same App ID and pushkey for different users. + */ + val append: Boolean, + + /** + * true to limit the push content to only id and not message content + * Ref: https://matrix.org/docs/spec/push_gateway/r0.1.1#homeserver-behaviour + */ + val withEventIdOnly: Boolean +) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/pushers/PushersService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/pushers/PushersService.kt index 5cb7857021..d7958ea3cd 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/pushers/PushersService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/pushers/PushersService.kt @@ -58,12 +58,14 @@ interface PushersService { * email pushers since we don't want to stop other accounts notifying to the same email address. * @throws [InvalidParameterException] if a parameter is not correct */ - suspend fun addEmailPusher(email: String, - lang: String, - emailBranding: String, - appDisplayName: String, - deviceDisplayName: String, - append: Boolean = true) + suspend fun addEmailPusher( + email: String, + lang: String, + emailBranding: String, + appDisplayName: String, + deviceDisplayName: String, + append: Boolean = true + ) /** * Directly ask the push gateway to send a push to this device. @@ -75,10 +77,12 @@ interface PushersService { * @param pushkey the FCM token * @param eventId the eventId which will be sent in the Push message. Use a fake eventId. */ - suspend fun testPush(url: String, - appId: String, - pushkey: String, - eventId: String) + suspend fun testPush( + url: String, + appId: String, + pushkey: String, + eventId: String + ) /** * Remove a registered pusher. @@ -107,61 +111,4 @@ interface PushersService { * Get the current pushers. */ fun getPushers(): List - - data class HttpPusher( - - /** - * This is a unique identifier for this pusher. The value you should use for - * this is the routing or destination address information for the notification, - * for example, the APNS token for APNS or the Registration ID for GCM. If your - * notification client has no such concept, use any unique identifier. Max length, 512 chars. - */ - val pushkey: String, - - /** - * The application id - * This is a reverse-DNS style identifier for the application. It is recommended - * that this end with the platform, such that different platform versions get - * different app identifiers. Max length, 64 chars. - */ - val appId: String, - - /** - * This string determines which set of device specific rules this pusher executes. - */ - val profileTag: String, - - /** - * The preferred language for receiving notifications (e.g. "en" or "en-US"). - */ - val lang: String, - - /** - * A human readable string that will allow the user to identify what application owns this pusher. - */ - val appDisplayName: String, - - /** - * A human readable string that will allow the user to identify what device owns this pusher. - */ - val deviceDisplayName: String, - - /** - * The URL to use to send notifications to. MUST be an HTTPS URL with a path of /_matrix/push/v1/notify. - */ - val url: String, - - /** - * If true, the homeserver should add another pusher with the given pushkey and App ID in addition - * to any others with different user IDs. Otherwise, the homeserver must remove any other pushers - * with the same App ID and pushkey for different users. - */ - val append: Boolean, - - /** - * true to limit the push content to only id and not message content - * Ref: https://matrix.org/docs/spec/push_gateway/r0.1.1#homeserver-behaviour - */ - val withEventIdOnly: Boolean - ) } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/pushrules/Action.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/pushrules/Action.kt index 2b2930c1ba..6122aae972 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/pushrules/Action.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/pushrules/Action.kt @@ -71,15 +71,15 @@ sealed class Action { fun List.toJson(): List { return map { action -> when (action) { - is Action.Notify -> Action.ACTION_NOTIFY + is Action.Notify -> Action.ACTION_NOTIFY is Action.DoNotNotify -> Action.ACTION_DONT_NOTIFY - is Action.Sound -> { + is Action.Sound -> { mapOf( Action.ACTION_OBJECT_SET_TWEAK_KEY to Action.ACTION_OBJECT_SET_TWEAK_VALUE_SOUND, Action.ACTION_OBJECT_VALUE_KEY to action.sound ) } - is Action.Highlight -> { + is Action.Highlight -> { mapOf( Action.ACTION_OBJECT_SET_TWEAK_KEY to Action.ACTION_OBJECT_SET_TWEAK_VALUE_HIGHLIGHT, Action.ACTION_OBJECT_VALUE_KEY to action.highlight @@ -94,11 +94,11 @@ fun PushRule.getActions(): List { actions.forEach { actionStrOrObj -> when (actionStrOrObj) { - Action.ACTION_NOTIFY -> Action.Notify + Action.ACTION_NOTIFY -> Action.Notify Action.ACTION_DONT_NOTIFY -> Action.DoNotNotify - is Map<*, *> -> { + is Map<*, *> -> { when (actionStrOrObj[Action.ACTION_OBJECT_SET_TWEAK_KEY]) { - Action.ACTION_OBJECT_SET_TWEAK_VALUE_SOUND -> { + Action.ACTION_OBJECT_SET_TWEAK_VALUE_SOUND -> { (actionStrOrObj[Action.ACTION_OBJECT_VALUE_KEY] as? String)?.let { stringValue -> Action.Sound(stringValue) } @@ -112,13 +112,13 @@ fun PushRule.getActions(): List { // When the value is not there, default is true, says the spec ?: Action.Highlight(true) } - else -> { + else -> { Timber.w("Unsupported set_tweak value ${actionStrOrObj[Action.ACTION_OBJECT_SET_TWEAK_KEY]}") null } } } - else -> { + else -> { Timber.w("Unsupported action type $actionStrOrObj") null } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/pushrules/ConditionResolver.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/pushrules/ConditionResolver.kt index f8a930f987..d64ee5f777 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/pushrules/ConditionResolver.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/pushrules/ConditionResolver.kt @@ -22,15 +22,23 @@ import org.matrix.android.sdk.api.session.events.model.Event * This class as all required context needed to evaluate rules */ interface ConditionResolver { - fun resolveEventMatchCondition(event: Event, - condition: EventMatchCondition): Boolean + fun resolveEventMatchCondition( + event: Event, + condition: EventMatchCondition + ): Boolean - fun resolveRoomMemberCountCondition(event: Event, - condition: RoomMemberCountCondition): Boolean + fun resolveRoomMemberCountCondition( + event: Event, + condition: RoomMemberCountCondition + ): Boolean - fun resolveSenderNotificationPermissionCondition(event: Event, - condition: SenderNotificationPermissionCondition): Boolean + fun resolveSenderNotificationPermissionCondition( + event: Event, + condition: SenderNotificationPermissionCondition + ): Boolean - fun resolveContainsDisplayNameCondition(event: Event, - condition: ContainsDisplayNameCondition): Boolean + fun resolveContainsDisplayNameCondition( + event: Event, + condition: ContainsDisplayNameCondition + ): Boolean } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/pushrules/ContainsDisplayNameCondition.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/pushrules/ContainsDisplayNameCondition.kt index 69dd14ddc2..0bf14345b9 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/pushrules/ContainsDisplayNameCondition.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/pushrules/ContainsDisplayNameCondition.kt @@ -39,7 +39,7 @@ class ContainsDisplayNameCondition : Condition { // EventType.ENCRYPTED -> { // event.root.getClearContent()?.toModel() // } - else -> null + else -> null } ?: return false return message.body.caseInsensitiveFind(displayName) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/pushrules/Kind.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/pushrules/Kind.kt index 463f3c2a73..bba6fe6026 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/pushrules/Kind.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/pushrules/Kind.kt @@ -27,11 +27,11 @@ enum class Kind(val value: String) { fun fromString(value: String): Kind { return when (value) { - "event_match" -> EventMatch - "contains_display_name" -> ContainsDisplayName - "room_member_count" -> RoomMemberCount + "event_match" -> EventMatch + "contains_display_name" -> ContainsDisplayName + "room_member_count" -> RoomMemberCount "sender_notification_permission" -> SenderNotificationPermission - else -> Unrecognised + else -> Unrecognised } } } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/pushrules/PushRuleService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/pushrules/PushRuleService.kt index 7ffbc89559..8f9c25fd30 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/pushrules/PushRuleService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/pushrules/PushRuleService.kt @@ -51,8 +51,10 @@ interface PushRuleService { // fun fulfilledBingRule(event: Event, rules: List): PushRule? - fun resolveSenderNotificationPermissionCondition(event: Event, - condition: SenderNotificationPermissionCondition): Boolean + fun resolveSenderNotificationPermissionCondition( + event: Event, + condition: SenderNotificationPermissionCondition + ): Boolean interface PushRuleListener { fun onEvents(pushEvents: PushEvents) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/pushrules/RoomMemberCountCondition.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/pushrules/RoomMemberCountCondition.kt index 6973ff1372..db097fd92c 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/pushrules/RoomMemberCountCondition.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/pushrules/RoomMemberCountCondition.kt @@ -47,8 +47,8 @@ class RoomMemberCountCondition( val numMembers = room.membershipService().getNumberOfJoinedMembers() return when (prefix) { - "<" -> numMembers < count - ">" -> numMembers > count + "<" -> numMembers < count + ">" -> numMembers > count "<=" -> numMembers <= count ">=" -> numMembers >= count else -> numMembers == count diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/pushrules/rest/PushCondition.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/pushrules/rest/PushCondition.kt index 1fc8329535..ec0936e4c8 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/pushrules/rest/PushCondition.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/pushrules/rest/PushCondition.kt @@ -61,7 +61,7 @@ data class PushCondition( fun asExecutableCondition(rule: PushRule): Condition? { return when (Kind.fromString(kind)) { - Kind.EventMatch -> { + Kind.EventMatch -> { if (key != null && pattern != null) { EventMatchCondition(key, pattern, rule.ruleId == RuleIds.RULE_ID_CONTAIN_USER_NAME) } else { @@ -69,10 +69,10 @@ data class PushCondition( null } } - Kind.ContainsDisplayName -> { + Kind.ContainsDisplayName -> { ContainsDisplayNameCondition() } - Kind.RoomMemberCount -> { + Kind.RoomMemberCount -> { if (iz.isNullOrEmpty()) { Timber.e("Malformed ROOM_MEMBER_COUNT condition") null @@ -88,7 +88,7 @@ data class PushCondition( SenderNotificationPermissionCondition(key) } } - Kind.Unrecognised -> { + Kind.Unrecognised -> { Timber.e("Unknown kind $kind") null } 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 3a18cf1497..5d2769ac3c 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 @@ -22,6 +22,7 @@ import org.matrix.android.sdk.api.session.room.accountdata.RoomAccountDataServic 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.location.LocationSharingService 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.relation.RelationService @@ -163,4 +164,9 @@ interface Room { * Get the RoomVersionService associated to this Room. */ fun roomVersionService(): RoomVersionService + + /** + * Get the LocationSharingService associated to this Room. + */ + fun locationSharingService(): LocationSharingService } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/RoomDirectoryService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/RoomDirectoryService.kt index cb70603e66..77092c4811 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/RoomDirectoryService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/RoomDirectoryService.kt @@ -28,8 +28,10 @@ interface RoomDirectoryService { /** * Get rooms from directory. */ - suspend fun getPublicRooms(server: String?, - publicRoomsParams: PublicRoomsParams): PublicRoomsResponse + suspend fun getPublicRooms( + server: String?, + publicRoomsParams: PublicRoomsParams + ): PublicRoomsResponse /** * Get the visibility of a room in the directory. 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 index 0e631427bd..b30c60554f 100644 --- 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 @@ -16,18 +16,21 @@ package org.matrix.android.sdk.api.session.room -import org.matrix.android.sdk.api.query.QueryStringValue +import org.matrix.android.sdk.api.query.QueryStateEventValue 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. + * @param eventId The id of the event to retrieve */ fun Room.getTimelineEvent(eventId: String): TimelineEvent? = timelineService().getTimelineEvent(eventId) /** * Get a StateEvent using the StateService of a Room. + * @param eventType The type of the event, see [org.matrix.android.sdk.api.session.events.model.EventType]. + * @param stateKey the query which will be done on the stateKey. */ -fun Room.getStateEvent(eventType: String, stateKey: QueryStringValue = QueryStringValue.NoCondition): Event? = +fun Room.getStateEvent(eventType: String, stateKey: QueryStateEventValue): Event? = stateService().getStateEvent(eventType, stateKey) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/RoomService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/RoomService.kt index 6d5551ddf0..5dfb8961e3 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/RoomService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/RoomService.kt @@ -60,9 +60,11 @@ interface RoomService { * @param reason optional reason for joining the room * @param viaServers the servers to attempt to join the room through. One of the servers must be participating in the room. */ - suspend fun joinRoom(roomIdOrAlias: String, - reason: String? = null, - viaServers: List = emptyList()) + suspend fun joinRoom( + roomIdOrAlias: String, + reason: String? = null, + viaServers: List = emptyList() + ) /** * @param roomId the roomId of the room to join @@ -97,19 +99,29 @@ interface RoomService { */ fun getRoomSummary(roomIdOrAlias: String): RoomSummary? + /** + * A live [RoomSummary] associated with the room with id [roomId]. + * You can observe this summary to get dynamic data from this room, even if the room is not joined yet + */ + fun getRoomSummaryLive(roomId: String): LiveData> + /** * Get a snapshot list of room summaries. * @return the immutable list of [RoomSummary] */ - fun getRoomSummaries(queryParams: RoomSummaryQueryParams, - sortOrder: RoomSortOrder = RoomSortOrder.NONE): List + fun getRoomSummaries( + queryParams: RoomSummaryQueryParams, + sortOrder: RoomSortOrder = RoomSortOrder.NONE + ): List /** * Get a live list of room summaries. This list is refreshed as soon as the data changes. * @return the [LiveData] of List[RoomSummary] */ - fun getRoomSummariesLive(queryParams: RoomSummaryQueryParams, - sortOrder: RoomSortOrder = RoomSortOrder.ACTIVITY): LiveData> + fun getRoomSummariesLive( + queryParams: RoomSummaryQueryParams, + sortOrder: RoomSortOrder = RoomSortOrder.ACTIVITY + ): LiveData> /** * Get a snapshot list of Breadcrumbs. @@ -139,8 +151,10 @@ interface RoomService { /** * Resolve a room alias to a room ID. */ - suspend fun getRoomIdByAlias(roomAlias: String, - searchOnServer: Boolean): Optional + suspend fun getRoomIdByAlias( + roomAlias: String, + searchOnServer: Boolean + ): Optional /** * Delete a room alias. @@ -205,16 +219,27 @@ interface RoomService { /** * TODO Doc. */ - fun getPagedRoomSummariesLive(queryParams: RoomSummaryQueryParams, - pagedListConfig: PagedList.Config = defaultPagedListConfig, - sortOrder: RoomSortOrder = RoomSortOrder.ACTIVITY): LiveData> + fun getPagedRoomSummariesLive( + queryParams: RoomSummaryQueryParams, + pagedListConfig: PagedList.Config = defaultPagedListConfig, + sortOrder: RoomSortOrder = RoomSortOrder.ACTIVITY + ): LiveData> /** - * TODO Doc. + * Get's a live paged list from a filter that can be dynamically updated. + * + * @param queryParams The filter to use + * @param pagedListConfig The paged list configuration (page size, initial load, prefetch distance...) + * @param sortOrder defines how to sort the results + * @param getFlattenParents When true, the list of known parents and grand parents summaries will be resolved. + * This can have significant impact on performance, better be used only on manageable list (filtered by displayName, ..). */ - fun getFilteredPagedRoomSummariesLive(queryParams: RoomSummaryQueryParams, - pagedListConfig: PagedList.Config = defaultPagedListConfig, - sortOrder: RoomSortOrder = RoomSortOrder.ACTIVITY): UpdatableLivePageResult + fun getFilteredPagedRoomSummariesLive( + queryParams: RoomSummaryQueryParams, + pagedListConfig: PagedList.Config = defaultPagedListConfig, + sortOrder: RoomSortOrder = RoomSortOrder.ACTIVITY, + getFlattenParents: Boolean = false, + ): UpdatableLivePageResult /** * Return a LiveData on the number of rooms. @@ -240,8 +265,10 @@ interface RoomService { /** * Returns all the children of this space, as LiveData. */ - fun getFlattenRoomSummaryChildrenOfLive(spaceId: String?, - memberships: List = Membership.activeMemberships()): LiveData> + fun getFlattenRoomSummaryChildrenOfLive( + spaceId: String?, + memberships: List = Membership.activeMemberships() + ): LiveData> /** * Refreshes the RoomSummary LatestPreviewContent for the given @param roomId. diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/RoomSortOrder.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/RoomSortOrder.kt index e721abd6a0..9368ad6bf4 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/RoomSortOrder.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/RoomSortOrder.kt @@ -16,9 +16,28 @@ package org.matrix.android.sdk.api.session.room +/** + * Enum to sort room list. + */ enum class RoomSortOrder { + /** + * Sort room list by room ascending name. + */ NAME, + + /** + * Sort room list by room descending last activity. + */ ACTIVITY, + + /** + * Sort room list by room priority and last activity: favorite room first, low priority room last, + * then descending last activity. + */ PRIORITY_AND_ACTIVITY, + + /** + * Do not sort room list. Useful if the order does not matter. Order can be indeterminate. + */ NONE } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/RoomSummaryQueryParams.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/RoomSummaryQueryParams.kt index 5c74dcced1..3d943473e4 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/RoomSummaryQueryParams.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/RoomSummaryQueryParams.kt @@ -16,60 +16,99 @@ package org.matrix.android.sdk.api.session.room -import org.matrix.android.sdk.api.query.ActiveSpaceFilter import org.matrix.android.sdk.api.query.QueryStringValue import org.matrix.android.sdk.api.query.RoomCategoryFilter import org.matrix.android.sdk.api.query.RoomTagQueryFilter +import org.matrix.android.sdk.api.query.SpaceFilter import org.matrix.android.sdk.api.session.room.model.Membership import org.matrix.android.sdk.api.session.room.model.RoomType import org.matrix.android.sdk.api.session.space.SpaceSummaryQueryParams +/** + * Create a [RoomSummaryQueryParams] object, calling [init] with a [RoomSummaryQueryParams.Builder]. + */ fun roomSummaryQueryParams(init: (RoomSummaryQueryParams.Builder.() -> Unit) = {}): RoomSummaryQueryParams { - return RoomSummaryQueryParams.Builder().apply(init).build() -} - -fun spaceSummaryQueryParams(init: (RoomSummaryQueryParams.Builder.() -> Unit) = {}): SpaceSummaryQueryParams { return RoomSummaryQueryParams.Builder() .apply(init) - .apply { - includeType = listOf(RoomType.SPACE) - excludeType = null - roomCategoryFilter = RoomCategoryFilter.ONLY_ROOMS - } .build() } /** - * This class can be used to filter room summaries to use with: - * [org.matrix.android.sdk.api.session.room.Room] and [org.matrix.android.sdk.api.session.room.RoomService]. + * Create a [SpaceSummaryQueryParams] object (which is a [RoomSummaryQueryParams]), calling [init] with a [RoomSummaryQueryParams.Builder]. + * This is specific for spaces, other filters will be applied after invoking [init] + */ +fun spaceSummaryQueryParams(init: (RoomSummaryQueryParams.Builder.() -> Unit) = {}): SpaceSummaryQueryParams { + return roomSummaryQueryParams { + init() + includeType = listOf(RoomType.SPACE) + excludeType = null + roomCategoryFilter = RoomCategoryFilter.ONLY_ROOMS + } +} + +/** + * This class can be used to filter room summaries to use with [RoomService]. + * It provides a [Builder]. + * [roomSummaryQueryParams] and [spaceSummaryQueryParams] can also be used to build an instance of this class. */ data class RoomSummaryQueryParams( - val roomId: QueryStringValue, + /** + * Query for the displayName of the room. The display name can be the value of the state event, + * or a value returned by [org.matrix.android.sdk.api.RoomDisplayNameFallbackProvider]. + */ val displayName: QueryStringValue, + /** + * Query for the canonical alias of the room. + */ val canonicalAlias: QueryStringValue, + /** + * Used to filter room by membership. + */ val memberships: List, + /** + * Used to filter room by room category. + */ val roomCategoryFilter: RoomCategoryFilter?, + /** + * Used to filter room by room tag. + */ val roomTagQueryFilter: RoomTagQueryFilter?, + /** + * Used to filter room by room type. + * @see [includeType] + */ val excludeType: List?, + /** + * Used to filter room by room type. + * @see [excludeType] + */ val includeType: List?, - val activeSpaceFilter: ActiveSpaceFilter?, + /** + * Used to filter room using the current space. + */ + val spaceFilter: SpaceFilter?, + /** + * Used to filter room using the current group. + */ val activeGroupId: String? = null ) { + /** + * Builder for [RoomSummaryQueryParams]. + * [roomSummaryQueryParams] and [spaceSummaryQueryParams] can also be used to build an instance of [RoomSummaryQueryParams]. + */ class Builder { - var roomId: QueryStringValue = QueryStringValue.IsNotEmpty - var displayName: QueryStringValue = QueryStringValue.IsNotEmpty + var displayName: QueryStringValue = QueryStringValue.NoCondition var canonicalAlias: QueryStringValue = QueryStringValue.NoCondition var memberships: List = Membership.all() - var roomCategoryFilter: RoomCategoryFilter? = RoomCategoryFilter.ALL + var roomCategoryFilter: RoomCategoryFilter? = null var roomTagQueryFilter: RoomTagQueryFilter? = null var excludeType: List? = listOf(RoomType.SPACE) var includeType: List? = null - var activeSpaceFilter: ActiveSpaceFilter = ActiveSpaceFilter.None + var spaceFilter: SpaceFilter? = null var activeGroupId: String? = null fun build() = RoomSummaryQueryParams( - roomId = roomId, displayName = displayName, canonicalAlias = canonicalAlias, memberships = memberships, @@ -77,7 +116,7 @@ data class RoomSummaryQueryParams( roomTagQueryFilter = roomTagQueryFilter, excludeType = excludeType, includeType = includeType, - activeSpaceFilter = activeSpaceFilter, + spaceFilter = spaceFilter, activeGroupId = activeGroupId ) } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/location/LocationSharingService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/location/LocationSharingService.kt new file mode 100644 index 0000000000..ada3dc85d7 --- /dev/null +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/location/LocationSharingService.kt @@ -0,0 +1,73 @@ +/* + * 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.location + +import androidx.annotation.MainThread +import androidx.lifecycle.LiveData +import org.matrix.android.sdk.api.session.room.model.livelocation.LiveLocationShareAggregatedSummary +import org.matrix.android.sdk.api.util.Cancelable +import org.matrix.android.sdk.api.util.Optional + +/** + * Manage all location sharing related features. + */ +interface LocationSharingService { + /** + * Send a static location event to the room. + * @param latitude required latitude of the location + * @param longitude required longitude of the location + * @param uncertainty Accuracy of the location in meters + * @param isUserLocation indicates whether the location data corresponds to the user location or not (pinned location) + */ + suspend fun sendStaticLocation(latitude: Double, longitude: Double, uncertainty: Double?, isUserLocation: Boolean): Cancelable + + /** + * Send a live location event to the room. + * To get the beacon info event id, [startLiveLocationShare] must be called before sending live location updates. + * @param beaconInfoEventId event id of the initial beacon info state event + * @param latitude required latitude of the location + * @param longitude required longitude of the location + * @param uncertainty Accuracy of the location in meters + */ + suspend fun sendLiveLocation(beaconInfoEventId: String, latitude: Double, longitude: Double, uncertainty: Double?): Cancelable + + /** + * Starts sharing live location in the room. + * @param timeoutMillis timeout of the live in milliseconds + * @return the result of the update of the live + */ + suspend fun startLiveLocationShare(timeoutMillis: Long): UpdateLiveLocationShareResult + + /** + * Stops sharing live location in the room. + * @return the result of the update of the live + */ + suspend fun stopLiveLocationShare(): UpdateLiveLocationShareResult + + /** + * Returns a LiveData on the list of current running live location shares. + */ + @MainThread + fun getRunningLiveLocationShareSummaries(): LiveData> + + /** + * Returns a LiveData on the live location share summary with the given eventId. + * @param beaconInfoEventId event id of the initial beacon info state event + */ + @MainThread + fun getLiveLocationShareSummary(beaconInfoEventId: String): LiveData> +} diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/location/UpdateLiveLocationShareResult.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/location/UpdateLiveLocationShareResult.kt new file mode 100644 index 0000000000..6f8c03be46 --- /dev/null +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/location/UpdateLiveLocationShareResult.kt @@ -0,0 +1,25 @@ +/* + * 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.location + +/** + * Represents the result of an update of live location share like a start or a stop. + */ +sealed interface UpdateLiveLocationShareResult { + data class Success(val beaconEventId: String) : UpdateLiveLocationShareResult + data class Failure(val error: Throwable) : UpdateLiveLocationShareResult +} diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/PowerLevelsContent.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/PowerLevelsContent.kt index 8ef94b2896..0329828130 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/PowerLevelsContent.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/PowerLevelsContent.kt @@ -95,8 +95,8 @@ data class PowerLevelsContent( // the first implementation was a string value is String -> value.toInt() is Double -> value.toInt() - is Int -> value - else -> Role.Moderator.value + is Int -> value + else -> Role.Moderator.value } } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/RoomGuestAccessContent.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/RoomGuestAccessContent.kt index ba274325bc..7dd853d75d 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/RoomGuestAccessContent.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/RoomGuestAccessContent.kt @@ -30,9 +30,9 @@ data class RoomGuestAccessContent( @Json(name = "guest_access") val guestAccessStr: String? = null ) { val guestAccess: GuestAccess? = when (guestAccessStr) { - "can_join" -> GuestAccess.CanJoin + "can_join" -> GuestAccess.CanJoin "forbidden" -> GuestAccess.Forbidden - else -> { + else -> { Timber.w("Invalid value for GuestAccess: `$guestAccessStr`") null } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/RoomHistoryVisibilityContent.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/RoomHistoryVisibilityContent.kt index da5c90ff05..39b4722c0c 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/RoomHistoryVisibilityContent.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/RoomHistoryVisibilityContent.kt @@ -26,10 +26,10 @@ data class RoomHistoryVisibilityContent( ) { val historyVisibility: RoomHistoryVisibility? = when (historyVisibilityStr) { "world_readable" -> RoomHistoryVisibility.WORLD_READABLE - "shared" -> RoomHistoryVisibility.SHARED - "invited" -> RoomHistoryVisibility.INVITED - "joined" -> RoomHistoryVisibility.JOINED - else -> { + "shared" -> RoomHistoryVisibility.SHARED + "invited" -> RoomHistoryVisibility.INVITED + "joined" -> RoomHistoryVisibility.JOINED + else -> { Timber.w("Invalid value for RoomHistoryVisibility: `$historyVisibilityStr`") null } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/RoomJoinRulesContent.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/RoomJoinRulesContent.kt index 3b338a36cd..dbe21b1633 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,12 +36,12 @@ data class RoomJoinRulesContent( @Json(name = "allow") val allowList: List? = null ) { val joinRules: RoomJoinRules? = when (joinRulesStr) { - "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 -> { + else -> { Timber.w("Invalid value for RoomJoinRules: `$joinRulesStr`") null } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/RoomSummary.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/RoomSummary.kt index 71c1d8303e..1ab23b7a11 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/RoomSummary.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/RoomSummary.kt @@ -28,65 +28,200 @@ import org.matrix.android.sdk.api.session.room.timeline.TimelineEvent * It can be retrieved by [org.matrix.android.sdk.api.session.room.Room] and [org.matrix.android.sdk.api.session.room.RoomService] */ data class RoomSummary( + /** + * The roomId of the room. + */ val roomId: String, - // Computed display name + /** + * Computed display name. The value of the state event `m.room.name` if not empty, else can be the value returned + * by [org.matrix.android.sdk.api.RoomDisplayNameFallbackProvider]. + */ val displayName: String = "", + /** + * The value of the live state event `m.room.name`. + */ val name: String = "", + /** + * The value of the live state event `m.room.topic`. + */ val topic: String = "", + /** + * The value of the live state event `m.room.avatar`. + */ val avatarUrl: String = "", + /** + * The value of the live state event `m.room.canonical_alias`. + */ val canonicalAlias: String? = null, + /** + * The list of all the aliases of this room. Content of the live state event `m.room.aliases`. + */ val aliases: List = emptyList(), + /** + * The value of the live state event `m.room.join_rules`. + */ val joinRules: RoomJoinRules? = null, + /** + * True is this room is referenced in the account data `m.direct`. + */ val isDirect: Boolean = false, + /** + * If [isDirect] is true, this is the id of the first other member of this room. + */ val directUserId: String? = null, + /** + * If [isDirect] is true, this it the presence of the first other member of this room. + */ val directUserPresence: UserPresence? = null, + /** + * Number of members who have joined this room. + */ val joinedMembersCount: Int? = 0, + /** + * Number of members who are invited to this room. + */ val invitedMembersCount: Int? = 0, + /** + * Latest [TimelineEvent] which can be displayed in this room. Can be used in the room list. + */ val latestPreviewableEvent: TimelineEvent? = null, + /** + * List of other member ids of this room. + */ val otherMemberIds: List = emptyList(), + /** + * Number of unread message in this room. + */ val notificationCount: Int = 0, + /** + * Number of unread and highlighted message in this room. + */ val highlightCount: Int = 0, + /** + * True if this room has unread messages. + */ val hasUnreadMessages: Boolean = false, + /** + * List of tags in this room. + */ val tags: List = emptyList(), + /** + * Current user membership in this room. + */ val membership: Membership = Membership.NONE, + /** + * Versioning state of this room. + */ val versioningState: VersioningState = VersioningState.NONE, + /** + * Value of `m.fully_read` for this room. + */ val readMarkerId: String? = null, + /** + * Message saved as draft for this room. + */ val userDrafts: List = emptyList(), + /** + * True if this room is encrypted. + */ val isEncrypted: Boolean, + /** + * Timestamp of the `m.room.encryption` state event. + */ val encryptionEventTs: Long?, + /** + * List of users who are currently typing on this room. + */ val typingUsers: List, + /** + * UserId of the user who has invited the current user to this room. + */ val inviterId: String? = null, + /** + * Breadcrumb index, util to sort rooms by last seen. + */ val breadcrumbsIndex: Int = NOT_IN_BREADCRUMBS, + /** + * The room encryption trust level. + * @see [RoomEncryptionTrustLevel] + */ val roomEncryptionTrustLevel: RoomEncryptionTrustLevel? = null, + /** + * True if a message has not been sent in this room. + */ val hasFailedSending: Boolean = false, + /** + * The type of the room. Null for regular room. + * @see [RoomType] + */ val roomType: String? = null, + /** + * List of parent spaces. + */ val spaceParents: List? = null, + /** + * List of children space. + */ val spaceChildren: List? = null, + /** + * List of all the space parents. Will be empty by default, you have to explicitly request it. + */ + val flattenParents: List = emptyList(), + /** + * List of all the space parent Ids. + */ val flattenParentIds: List = emptyList(), - val roomEncryptionAlgorithm: RoomEncryptionAlgorithm? = null + /** + * Information about the encryption algorithm, if this room is encrypted. + */ + val roomEncryptionAlgorithm: RoomEncryptionAlgorithm? = null, ) { - + /** + * True if [versioningState] is not [VersioningState.NONE]. + */ val isVersioned: Boolean get() = versioningState != VersioningState.NONE + /** + * True if [notificationCount] is not `0`. + */ val hasNewMessages: Boolean get() = notificationCount != 0 + /** + * True if the room has the tag `m.lowpriority`. + */ val isLowPriority: Boolean get() = hasTag(RoomTag.ROOM_TAG_LOW_PRIORITY) + /** + * True if the room has the tag `m.favourite`. + */ val isFavorite: Boolean get() = hasTag(RoomTag.ROOM_TAG_FAVOURITE) + /** + * True if [joinRules] is [RoomJoinRules.PUBLIC]. + */ val isPublic: Boolean get() = joinRules == RoomJoinRules.PUBLIC + /** + * Test if the room has the provided [tag]. + */ fun hasTag(tag: String) = tags.any { it.name == tag } + /** + * True if a 1-1 call can be started, i.e. the room has exactly 2 joined members. + */ val canStartCall: Boolean get() = joinedMembersCount == 2 companion object { + /** + * Constant to indicated that the room is not on the breadcrumbs. + * Used by [breadcrumbsIndex]. + */ const val NOT_IN_BREADCRUMBS = -1 } } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/VersioningState.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/VersioningState.kt index 6d15d1a007..8cfe3da031 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/VersioningState.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/VersioningState.kt @@ -16,9 +16,23 @@ package org.matrix.android.sdk.api.session.room.model +/** + * Enum for the versioning state of a room. + */ enum class VersioningState { + /** + * The room is not versioned. + */ NONE, + + /** + * The room has been upgraded, but the new room is not joined yet. + */ UPGRADED_ROOM_NOT_JOINED, + + /** + * The room has been upgraded, and the new room has been joined. + */ UPGRADED_ROOM_JOINED; fun isUpgraded() = this != NONE 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 index 0b28d62f56..5ad1a48217 100644 --- 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 @@ -22,6 +22,10 @@ import org.matrix.android.sdk.api.session.room.model.message.MessageBeaconLocati * Aggregation info concerning a live location share. */ data class LiveLocationShareAggregatedSummary( + val userId: String?, + /** + * Indicate whether the live is currently running. + */ 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/LocationInfo.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/LocationInfo.kt index a1fd3bd2ec..e0a7846167 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/LocationInfo.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/LocationInfo.kt @@ -22,7 +22,7 @@ import com.squareup.moshi.JsonClass @JsonClass(generateAdapter = true) data class LocationInfo( /** - * Required. RFC5870 formatted geo uri 'geo:latitude,longitude;uncertainty' like 'geo:40.05,29.24;30' representing this location. + * Required. RFC5870 formatted geo uri 'geo:latitude,longitude;u=uncertainty' like 'geo:40.05,29.24;u=30' representing this location. */ @Json(name = "uri") val geoUri: String? = null, 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 0a66a6e400..30420fd3c7 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 @@ -35,7 +35,7 @@ data class MessageLocationContent( @Json(name = "body") override val body: String, /** - * Required. RFC5870 formatted geo uri 'geo:latitude,longitude;uncertainty' like 'geo:40.05,29.24;30' representing this location. + * Required. RFC5870 formatted geo uri 'geo:latitude,longitude;u=uncertainty' like 'geo:40.05,29.24;u=30' representing this location. */ @Json(name = "geo_uri") val geoUri: String, diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/MessageVerificationAcceptContent.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/MessageVerificationAcceptContent.kt index 27619cf0a9..33f61648dc 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/MessageVerificationAcceptContent.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/MessageVerificationAcceptContent.kt @@ -40,12 +40,14 @@ internal data class MessageVerificationAcceptContent( companion object : VerificationInfoAcceptFactory { - override fun create(tid: String, - keyAgreementProtocol: String, - hash: String, - commitment: String, - messageAuthenticationCode: String, - shortAuthenticationStrings: List): VerificationInfoAccept { + override fun create( + tid: String, + keyAgreementProtocol: String, + hash: String, + commitment: String, + messageAuthenticationCode: String, + shortAuthenticationStrings: List + ): VerificationInfoAccept { return MessageVerificationAcceptContent( hash, keyAgreementProtocol, diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/PollCreationInfo.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/PollCreationInfo.kt index 81b034a809..ee31d5959e 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/PollCreationInfo.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/PollCreationInfo.kt @@ -25,4 +25,7 @@ data class PollCreationInfo( @Json(name = "kind") val kind: PollType? = PollType.DISCLOSED_UNSTABLE, @Json(name = "max_selections") val maxSelections: Int = 1, @Json(name = "answers") val answers: List? = null -) +) { + + fun isUndisclosed() = kind in listOf(PollType.UNDISCLOSED_UNSTABLE, PollType.UNDISCLOSED) +} diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/relation/RelationService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/relation/RelationService.kt index 02c597ee63..d34ea3c7d3 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/relation/RelationService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/relation/RelationService.kt @@ -58,16 +58,20 @@ interface RelationService { * @param targetEventId the id of the event being reacted * @param reaction the reaction (preferably emoji) */ - fun sendReaction(targetEventId: String, - reaction: String): Cancelable + fun sendReaction( + targetEventId: String, + reaction: String + ): Cancelable /** * Undo a reaction (emoji) to the targetedEvent. * @param targetEventId the id of the event being reacted * @param reaction the reaction (preferably emoji) */ - suspend fun undoReaction(targetEventId: String, - reaction: String): Cancelable + suspend fun undoReaction( + targetEventId: String, + reaction: String + ): Cancelable /** * Edit a poll. @@ -76,10 +80,12 @@ interface RelationService { * @param question The edited question * @param options The edited options */ - fun editPoll(targetEvent: TimelineEvent, - pollType: PollType, - question: String, - options: List): Cancelable + fun editPoll( + targetEvent: TimelineEvent, + pollType: PollType, + question: String, + options: List + ): Cancelable /** * Edit a text message body. Limited to "m.text" contentType. @@ -89,11 +95,13 @@ interface RelationService { * @param newBodyAutoMarkdown true to parse markdown on the new body * @param compatibilityBodyText The text that will appear on clients that don't support yet edition */ - fun editTextMessage(targetEvent: TimelineEvent, - msgType: String, - newBodyText: CharSequence, - newBodyAutoMarkdown: Boolean, - compatibilityBodyText: String = "* $newBodyText"): Cancelable + fun editTextMessage( + targetEvent: TimelineEvent, + msgType: String, + newBodyText: CharSequence, + newBodyAutoMarkdown: Boolean, + compatibilityBodyText: String = "* $newBodyText" + ): Cancelable /** * Edit a reply. This is a special case because replies contains fallback text as a prefix. @@ -103,10 +111,12 @@ interface RelationService { * @param newBodyText The edited body (stripped from in reply to content) * @param compatibilityBodyText The text that will appear on clients that don't support yet edition */ - fun editReply(replyToEdit: TimelineEvent, - originalTimelineEvent: TimelineEvent, - newBodyText: String, - compatibilityBodyText: String = "* $newBodyText"): Cancelable + fun editReply( + replyToEdit: TimelineEvent, + originalTimelineEvent: TimelineEvent, + newBodyText: String, + compatibilityBodyText: String = "* $newBodyText" + ): Cancelable /** * Get the edit history of the given event. @@ -127,11 +137,12 @@ interface RelationService { * @param showInThread If true, relation will be added to the reply in order to be visible from within threads * @param rootThreadEventId If show in thread is true then we need the rootThreadEventId to generate the relation */ - fun replyToMessage(eventReplied: TimelineEvent, - replyText: CharSequence, - autoMarkdown: Boolean = false, - showInThread: Boolean = false, - rootThreadEventId: String? = null + fun replyToMessage( + eventReplied: TimelineEvent, + replyText: CharSequence, + autoMarkdown: Boolean = false, + showInThread: Boolean = false, + rootThreadEventId: String? = null ): Cancelable? /** @@ -159,10 +170,12 @@ interface RelationService { * @param formattedText The formatted body using MessageType#FORMAT_MATRIX_HTML * @param eventReplied the event referenced by the reply within a thread */ - fun replyInThread(rootThreadEventId: String, - replyInThreadText: CharSequence, - msgType: String = MessageType.MSGTYPE_TEXT, - autoMarkdown: Boolean = false, - formattedText: String? = null, - eventReplied: TimelineEvent? = null): Cancelable? + fun replyInThread( + rootThreadEventId: String, + replyInThreadText: CharSequence, + msgType: String = MessageType.MSGTYPE_TEXT, + autoMarkdown: Boolean = false, + formattedText: String? = null, + eventReplied: TimelineEvent? = null + ): Cancelable? } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/powerlevels/Role.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/powerlevels/Role.kt index 5fe9bf6993..c5cc573458 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/powerlevels/Role.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/powerlevels/Role.kt @@ -32,11 +32,11 @@ sealed class Role(open val value: Int) : Comparable { // Order matters, default value should be checked after defined roles fun fromValue(value: Int, default: Int): Role { return when (value) { - Admin.value -> Admin + Admin.value -> Admin Moderator.value -> Moderator Default.value, - default -> Default - else -> Custom(value) + default -> Default + else -> Custom(value) } } } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/send/SendService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/send/SendService.kt index c2e3ded2fa..9cf062356f 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/send/SendService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/send/SendService.kt @@ -76,10 +76,12 @@ interface SendService { * @param rootThreadEventId when this param is not null, the Media will be sent in this specific thread * @return a [Cancelable] */ - fun sendMedia(attachment: ContentAttachmentData, - compressBeforeSending: Boolean, - roomIds: Set, - rootThreadEventId: String? = null): Cancelable + fun sendMedia( + attachment: ContentAttachmentData, + compressBeforeSending: Boolean, + roomIds: Set, + rootThreadEventId: String? = null + ): Cancelable /** * Method to send a list of media asynchronously. @@ -90,10 +92,12 @@ interface SendService { * @param rootThreadEventId when this param is not null, all the Media will be sent in this specific thread * @return a [Cancelable] */ - fun sendMedias(attachments: List, - compressBeforeSending: Boolean, - roomIds: Set, - rootThreadEventId: String? = null): Cancelable + fun sendMedias( + attachments: List, + compressBeforeSending: Boolean, + roomIds: Set, + rootThreadEventId: String? = null + ): Cancelable /** * Send a poll to the room. @@ -138,24 +142,6 @@ interface SendService { */ fun resendMediaMessage(localEcho: TimelineEvent): Cancelable - /** - * Send a location event to the room. - * @param latitude required latitude of the location - * @param longitude required longitude of the location - * @param uncertainty Accuracy of the location in meters - * @param isUserLocation indicates whether the location data corresponds to the user location or not - */ - fun sendLocation(latitude: Double, longitude: Double, uncertainty: Double?, isUserLocation: Boolean): Cancelable - - /** - * Send a live location event to the room. beacon_info state event has to be sent before sending live location updates. - * @param beaconInfoEventId event id of the initial beacon info state event - * @param latitude required latitude of the location - * @param longitude required longitude of the location - * @param uncertainty Accuracy of the location in meters - */ - fun sendLiveLocation(beaconInfoEventId: String, latitude: Double, longitude: Double, uncertainty: Double?): Cancelable - /** * Remove this failed message from the timeline. * @param localEcho the unsent local echo diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/send/SendState.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/send/SendState.kt index 7c806bf35b..d058ff2840 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/send/SendState.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/send/SendState.kt @@ -17,27 +17,44 @@ package org.matrix.android.sdk.api.session.room.send enum class SendState { + /** + * The state is unknown. + */ UNKNOWN, - // the event has not been sent + /** + * The event has not been sent. + */ UNSENT, - // the event is encrypting + /** + * The event is encrypting. + */ ENCRYPTING, - // the event is currently sending + /** + * The event is currently sending. + */ SENDING, - // the event has been sent + /** + * The event has been sent. + */ SENT, - // the event has been received from server + /** + * The event has been received from server. + */ SYNCED, - // The event failed to be sent + /** + * The event failed to be sent. + */ UNDELIVERED, - // the event failed to be sent because some unknown devices have been found while encrypting it + /** + * The event failed to be sent because some unknown devices have been found while encrypting it. + */ FAILED_UNKNOWN_DEVICES; internal companion object { diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/send/UserDraft.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/send/UserDraft.kt index 4ede1a66fc..e16405b3c0 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/send/UserDraft.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/send/UserDraft.kt @@ -33,7 +33,7 @@ sealed interface UserDraft { fun isValid(): Boolean { return when (this) { is Regular -> content.isNotBlank() - else -> true + else -> true } } } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/sender/SenderInfo.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/sender/SenderInfo.kt index 4c308c355a..d629df8b16 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/sender/SenderInfo.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/sender/SenderInfo.kt @@ -29,9 +29,9 @@ data class SenderInfo( ) { val disambiguatedDisplayName: String get() = when { - displayName == null -> userId + displayName == null -> userId displayName.replaceSpaceChars().isBlank() -> "$displayName ($userId)" - isUniqueDisplayName -> displayName - else -> "$displayName ($userId)" + isUniqueDisplayName -> displayName + else -> "$displayName ($userId)" } } 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 c79171f156..6ca63c2c49 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 @@ -18,7 +18,7 @@ package org.matrix.android.sdk.api.session.room.state import android.net.Uri import androidx.lifecycle.LiveData -import org.matrix.android.sdk.api.query.QueryStringValue +import org.matrix.android.sdk.api.query.QueryStateEventValue import org.matrix.android.sdk.api.session.events.model.Event import org.matrix.android.sdk.api.session.room.model.GuestAccess import org.matrix.android.sdk.api.session.room.model.RoomHistoryVisibility @@ -66,19 +66,6 @@ interface StateService { */ suspend fun deleteAvatar() - /** - * Stops sharing live location in the room. - * @param userId user id - */ - suspend fun stopLiveLocation(userId: String) - - /** - * Returns beacon info state event of a user. - * @param userId user id who is sharing location - * @param filterOnlyLive filters only ongoing live location sharing beacons if true else ended event is included - */ - suspend fun getLiveLocationBeaconInfo(userId: String, filterOnlyLive: Boolean): Event? - /** * Send a state event to the room. * @param eventType The type of event to send. @@ -93,28 +80,28 @@ interface StateService { * @param eventType An eventType. * @param stateKey the query which will be done on the stateKey */ - fun getStateEvent(eventType: String, stateKey: QueryStringValue = QueryStringValue.NoCondition): Event? + fun getStateEvent(eventType: String, stateKey: QueryStateEventValue): Event? /** * Get a live state event of the room. * @param eventType An eventType. * @param stateKey the query which will be done on the stateKey */ - fun getStateEventLive(eventType: String, stateKey: QueryStringValue = QueryStringValue.NoCondition): LiveData> + fun getStateEventLive(eventType: String, stateKey: QueryStateEventValue): LiveData> /** * Get state events of the room. * @param eventTypes Set of eventType. If empty, all state events will be returned * @param stateKey the query which will be done on the stateKey */ - fun getStateEvents(eventTypes: Set, stateKey: QueryStringValue = QueryStringValue.NoCondition): List + fun getStateEvents(eventTypes: Set, stateKey: QueryStateEventValue): List /** * Get live state events of the room. * @param eventTypes Set of eventType to observe. If empty, all state events will be observed * @param stateKey the query which will be done on the stateKey */ - fun getStateEventsLive(eventTypes: Set, stateKey: QueryStringValue = QueryStringValue.NoCondition): LiveData> + fun getStateEventsLive(eventTypes: Set, stateKey: QueryStateEventValue): LiveData> suspend fun setJoinRulePublic() suspend fun setJoinRuleInviteOnly() diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/state/StateServiceExtension.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/state/StateServiceExtension.kt index 9e45fc126d..6a9506fd9e 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/state/StateServiceExtension.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/state/StateServiceExtension.kt @@ -26,7 +26,7 @@ import org.matrix.android.sdk.api.session.room.model.RoomJoinRulesContent * Return true if a room can be joined by anyone (RoomJoinRules.PUBLIC). */ fun StateService.isPublic(): Boolean { - return getStateEvent(EventType.STATE_ROOM_JOIN_RULES, QueryStringValue.NoCondition) + return getStateEvent(EventType.STATE_ROOM_JOIN_RULES, QueryStringValue.IsEmpty) ?.content ?.toModel() ?.joinRules == RoomJoinRules.PUBLIC diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/threads/model/ThreadEditions.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/threads/model/ThreadEditions.kt index c8353cf0de..dc9cc886e9 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/threads/model/ThreadEditions.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/threads/model/ThreadEditions.kt @@ -16,5 +16,7 @@ package org.matrix.android.sdk.api.session.room.threads.model -data class ThreadEditions(var rootThreadEdition: String? = null, - var latestThreadEdition: String? = null) +data class ThreadEditions( + var rootThreadEdition: String? = null, + var latestThreadEdition: String? = null +) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/threads/model/ThreadSummary.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/threads/model/ThreadSummary.kt index 1ef972e889..0b1aea1966 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/threads/model/ThreadSummary.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/threads/model/ThreadSummary.kt @@ -22,12 +22,14 @@ import org.matrix.android.sdk.api.session.room.sender.SenderInfo /** * The main thread Summary model, mainly used to display the thread list. */ -data class ThreadSummary(val roomId: String, - val rootEvent: Event?, - val latestEvent: Event?, - val rootEventId: String, - val rootThreadSenderInfo: SenderInfo, - val latestThreadSenderInfo: SenderInfo, - val isUserParticipating: Boolean, - val numberOfThreads: Int, - val threadEditions: ThreadEditions = ThreadEditions()) +data class ThreadSummary( + val roomId: String, + val rootEvent: Event?, + val latestEvent: Event?, + val rootEventId: String, + val rootThreadSenderInfo: SenderInfo, + val latestThreadSenderInfo: SenderInfo, + val isUserParticipating: Boolean, + val numberOfThreads: Int, + val threadEditions: ThreadEditions = ThreadEditions() +) 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 d4ade9b5b9..9d8c8a13bd 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 @@ -139,11 +139,11 @@ fun TimelineEvent.getEditedEventId(): String? { */ fun TimelineEvent.getLastMessageContent(): MessageContent? { return when (root.getClearType()) { - EventType.STICKER -> root.getClearContent().toModel() - in EventType.POLL_START -> (annotations?.editSummary?.latestContent ?: root.getClearContent()).toModel() + 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.BEACON_LOCATION_DATA -> (annotations?.editSummary?.latestContent ?: root.getClearContent()).toModel() - else -> (annotations?.editSummary?.latestContent ?: root.getClearContent()).toModel() + in EventType.BEACON_LOCATION_DATA -> (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/search/SearchService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/search/SearchService.kt index bc1c9e5769..ffb8b1ca4d 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/search/SearchService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/search/SearchService.kt @@ -33,12 +33,14 @@ interface SearchService { * @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. */ - suspend fun search(searchTerm: String, - roomId: String, - nextBatch: String?, - orderByRecent: Boolean, - limit: Int, - beforeLimit: Int, - afterLimit: Int, - includeProfile: Boolean): SearchResult + suspend fun search( + searchTerm: String, + roomId: String, + nextBatch: String?, + orderByRecent: Boolean, + limit: Int, + beforeLimit: Int, + afterLimit: Int, + includeProfile: Boolean + ): SearchResult } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/securestorage/KeyRef.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/securestorage/KeyRef.kt new file mode 100644 index 0000000000..5a1bf67fdd --- /dev/null +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/securestorage/KeyRef.kt @@ -0,0 +1,22 @@ +/* + * 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.securestorage + +data class KeyRef( + val keyId: String?, + val keySpec: SsssKeySpec? +) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/securestorage/SharedSecretStorageService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/securestorage/SharedSecretStorageService.kt index e3a9860523..bdbbd3ea84 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/securestorage/SharedSecretStorageService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/securestorage/SharedSecretStorageService.kt @@ -44,10 +44,12 @@ interface SharedSecretStorageService { * * @return key creation info */ - suspend fun generateKey(keyId: String, - key: SsssKeySpec?, - keyName: String, - keySigner: KeySigner?): SsssKeyCreationInfo + suspend fun generateKey( + keyId: String, + key: SsssKeySpec?, + keyName: String, + keySigner: KeySigner? + ): SsssKeyCreationInfo /** * Generates a SSSS key using the given passphrase. @@ -61,11 +63,13 @@ interface SharedSecretStorageService { * * @return key creation info */ - suspend fun generateKeyWithPassphrase(keyId: String, - keyName: String, - passphrase: String, - keySigner: KeySigner, - progressListener: ProgressListener?): SsssKeyCreationInfo + suspend fun generateKeyWithPassphrase( + keyId: String, + keyName: String, + passphrase: String, + keySigner: KeySigner, + progressListener: ProgressListener? + ): SsssKeyCreationInfo fun getKey(keyId: String): KeyInfoResult @@ -132,9 +136,4 @@ interface SharedSecretStorageService { fun checkShouldBeAbleToAccessSecrets(secretNames: List, keyId: String?): IntegrityResult suspend fun requestSecret(name: String, myOtherDeviceId: String) - - data class KeyRef( - val keyId: String?, - val keySpec: SsssKeySpec? - ) } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/space/Space.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/space/Space.kt index c990388628..61c03e08fc 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/space/Space.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/space/Space.kt @@ -31,11 +31,13 @@ interface Space { */ fun spaceSummary(): RoomSummary? - suspend fun addChildren(roomId: String, - viaServers: List?, - order: String?, + suspend fun addChildren( + roomId: String, + viaServers: List?, + order: String?, // autoJoin: Boolean = false, - suggested: Boolean? = false) + suggested: Boolean? = false + ) fun getChildInfo(roomId: String): SpaceChildContent? 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 38e55664d2..c7a6405014 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 @@ -37,11 +37,13 @@ interface SpaceService { /** * Just a shortcut for space creation for ease of use. */ - suspend fun createSpace(name: String, - topic: String?, - avatarUri: Uri?, - isPublic: Boolean, - roomAliasLocalPart: String? = null): String + suspend fun createSpace( + name: String, + topic: String?, + avatarUri: Uri?, + isPublic: Boolean, + roomAliasLocalPart: String? = null + ): String /** * Get a space from a spaceId. @@ -68,25 +70,33 @@ interface SpaceService { * then the parameters given for suggested_only and max_depth must be the same. * @param knownStateList when paginating, pass back the m.space.child state events */ - suspend fun querySpaceChildren(spaceId: String, - suggestedOnly: Boolean? = null, - limit: Int? = null, - from: String? = null, - knownStateList: List? = null): SpaceHierarchyData + suspend fun querySpaceChildren( + spaceId: String, + suggestedOnly: Boolean? = null, + limit: Int? = null, + from: String? = null, + knownStateList: List? = null + ): SpaceHierarchyData /** * Get a live list of space summaries. This list is refreshed as soon as the data changes. * @return the [LiveData] of List[SpaceSummary] */ - fun getSpaceSummariesLive(queryParams: SpaceSummaryQueryParams, - sortOrder: RoomSortOrder = RoomSortOrder.NONE): LiveData> + fun getSpaceSummariesLive( + queryParams: SpaceSummaryQueryParams, + sortOrder: RoomSortOrder = RoomSortOrder.NONE + ): LiveData> - fun getSpaceSummaries(spaceSummaryQueryParams: SpaceSummaryQueryParams, - sortOrder: RoomSortOrder = RoomSortOrder.NONE): List + fun getSpaceSummaries( + spaceSummaryQueryParams: SpaceSummaryQueryParams, + sortOrder: RoomSortOrder = RoomSortOrder.NONE + ): List - suspend fun joinSpace(spaceIdOrAlias: String, - reason: String? = null, - viaServers: List = emptyList()): JoinSpaceResult + suspend fun joinSpace( + spaceIdOrAlias: String, + reason: String? = null, + viaServers: List = emptyList() + ): JoinSpaceResult suspend fun rejectInvite(spaceId: String, reason: String?) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/statistics/StatisticEvent.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/statistics/StatisticEvent.kt index 76755517ce..d9c7772393 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/statistics/StatisticEvent.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/statistics/StatisticEvent.kt @@ -23,15 +23,19 @@ sealed interface StatisticEvent { /** * Initial sync request, response downloading, and treatment (parsing and storage) of response. */ - data class InitialSyncRequest(val requestDurationMs: Int, - val downloadDurationMs: Int, - val treatmentDurationMs: Int, - val nbOfJoinedRooms: Int) : StatisticEvent + data class InitialSyncRequest( + val requestDurationMs: Int, + val downloadDurationMs: Int, + val treatmentDurationMs: Int, + val nbOfJoinedRooms: Int + ) : StatisticEvent /** * Incremental sync event. */ - data class SyncTreatment(val durationMs: Int, - val afterPause: Boolean, - val nbOfJoinedRooms: Int) : StatisticEvent + data class SyncTreatment( + val durationMs: Int, + val afterPause: Boolean, + val nbOfJoinedRooms: Int + ) : StatisticEvent } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/initsync/InitSyncStep.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/sync/InitialSyncStep.kt similarity index 91% rename from matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/initsync/InitSyncStep.kt rename to matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/sync/InitialSyncStep.kt index 901c1b2ffb..407585b003 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/initsync/InitSyncStep.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/sync/InitialSyncStep.kt @@ -14,9 +14,9 @@ * limitations under the License. */ -package org.matrix.android.sdk.api.session.initsync +package org.matrix.android.sdk.api.session.sync -enum class InitSyncStep { +enum class InitialSyncStep { ServerComputing, Downloading, ImportingAccount, diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/sync/SyncRequestState.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/sync/SyncRequestState.kt new file mode 100644 index 0000000000..4e932d49de --- /dev/null +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/sync/SyncRequestState.kt @@ -0,0 +1,44 @@ +/* + * 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.sync + +sealed interface SyncRequestState { + /** + * For initial sync. + */ + interface InitialSyncRequestState : SyncRequestState + + object Idle : InitialSyncRequestState + data class InitialSyncProgressing( + val initialSyncStep: InitialSyncStep, + val percentProgress: Int = 0 + ) : InitialSyncRequestState + + /** + * For incremental sync. + */ + interface IncrementalSyncRequestState : SyncRequestState + + object IncrementalSyncIdle : IncrementalSyncRequestState + data class IncrementalSyncParsing( + val rooms: Int, + val toDevice: Int + ) : IncrementalSyncRequestState + + object IncrementalSyncError : IncrementalSyncRequestState + object IncrementalSyncDone : IncrementalSyncRequestState +} diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/sync/SyncService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/sync/SyncService.kt new file mode 100644 index 0000000000..5b2bf651af --- /dev/null +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/sync/SyncService.kt @@ -0,0 +1,76 @@ +/* + * 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.sync + +import androidx.lifecycle.LiveData +import kotlinx.coroutines.flow.SharedFlow +import org.matrix.android.sdk.api.session.sync.model.SyncResponse + +interface SyncService { + /** + * This method start the sync thread. + */ + fun startSync(fromForeground: Boolean) + + /** + * This method stop the sync thread. + */ + fun stopSync() + + /** + * Requires a one time background sync. + */ + fun requireBackgroundSync() + + /** + * Launches infinite self rescheduling background syncs via the WorkManager. + * + * While dozing, syncs will only occur during maintenance windows. + * For reliability it's recommended to also start a long running foreground service + * along with disabling battery optimizations. + */ + fun startAutomaticBackgroundSync(timeOutInSeconds: Long, repeatDelayInSeconds: Long) + + fun stopAnyBackgroundSync() + + /** + * This method returns the current sync state. + * @return the current [SyncState]. + */ + fun getSyncState(): SyncState + + /** + * This method allows to listen the sync state. + * @return a [LiveData] of [SyncState]. + */ + fun getSyncStateLive(): LiveData + + /** + * Get the [SyncRequestState] as a LiveData. + */ + fun getSyncRequestStateLive(): LiveData + + /** + * This method returns a flow of SyncResponse. New value will be pushed through the sync thread. + */ + fun syncFlow(): SharedFlow + + /** + * This methods return true if an initial sync has been processed. + */ + fun hasAlreadySynced(): Boolean +} diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/sync/job/SyncService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/sync/job/SyncAndroidService.kt similarity index 97% rename from matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/sync/job/SyncService.kt rename to matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/sync/job/SyncAndroidService.kt index ac81be2174..8dee633b9c 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/sync/job/SyncService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/sync/job/SyncAndroidService.kt @@ -46,7 +46,7 @@ import java.util.concurrent.atomic.AtomicBoolean * in order to be able to perform a sync even if the app is not running. * The and must be declared in the Manifest or the app using the SDK */ -abstract class SyncService : Service() { +abstract class SyncAndroidService : Service() { private var sessionId: String? = null private var mIsSelfDestroyed: Boolean = false @@ -84,7 +84,7 @@ abstract class SyncService : Service() { stopMe() } } - else -> { + else -> { val isInit = initialize(intent) onStart(isInitialSync) if (isInit) { @@ -158,9 +158,9 @@ abstract class SyncService : Service() { // never do that in foreground, let the syncThread work syncTask.execute(params) // Start sync if we were doing an initial sync and the syncThread is not launched yet - if (isInitialSync && session.getSyncState() == SyncState.Idle) { + if (isInitialSync && session.syncService().getSyncState() == SyncState.Idle) { val isForeground = !backgroundDetectionObserver.isInBackground - session.startSync(isForeground) + session.syncService().startSync(isForeground) } stopMe() } catch (throwable: Throwable) { @@ -210,7 +210,7 @@ abstract class SyncService : Service() { session = sessionComponent.session() sessionId = safeSessionId syncTask = sessionComponent.syncTask() - isInitialSync = !session.hasAlreadySynced() + isInitialSync = !session.syncService().hasAlreadySynced() networkConnectivityChecker = sessionComponent.networkConnectivityChecker() taskExecutor = sessionComponent.taskExecutor() coroutineDispatchers = sessionComponent.coroutineDispatchers() diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/sync/model/RoomSyncUnreadNotifications.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/sync/model/RoomSyncUnreadNotifications.kt index 6618bceacd..09273f56e6 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/sync/model/RoomSyncUnreadNotifications.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/sync/model/RoomSyncUnreadNotifications.kt @@ -38,4 +38,5 @@ data class RoomSyncUnreadNotifications( /** * The number of highlighted unread messages (subset of notifications). */ - @Json(name = "highlight_count") val highlightCount: Int? = null) + @Json(name = "highlight_count") val highlightCount: Int? = null +) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/terms/TermsResponse.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/terms/TermsResponse.kt index 9a30b4d764..ae728326cc 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/terms/TermsResponse.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/terms/TermsResponse.kt @@ -30,8 +30,10 @@ data class TermsResponse( val policies: JsonDict? = null ) { - fun getLocalizedTerms(userLanguage: String, - defaultLanguage: String = "en"): List { + fun getLocalizedTerms( + userLanguage: String, + defaultLanguage: String = "en" + ): List { return policies?.map { val tos = policies[it.key] as? Map<*, *> ?: return@map null ((tos[userLanguage] ?: tos[defaultLanguage]) as? Map<*, *>)?.let { termsMap -> diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/terms/TermsService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/terms/TermsService.kt index 6c357b2224..1ddce763d1 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/terms/TermsService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/terms/TermsService.kt @@ -24,10 +24,12 @@ interface TermsService { suspend fun getTerms(serviceType: ServiceType, baseUrl: String): GetTermsResponse - suspend fun agreeToTerms(serviceType: ServiceType, - baseUrl: String, - agreedUrls: List, - token: String?) + suspend fun agreeToTerms( + serviceType: ServiceType, + baseUrl: String, + agreedUrls: List, + token: String? + ) /** * Get the homeserver terms, from the register API. diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/threads/ThreadNotificationState.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/threads/ThreadNotificationState.kt index c110802d23..6825f8c279 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/threads/ThreadNotificationState.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/threads/ThreadNotificationState.kt @@ -20,14 +20,19 @@ package org.matrix.android.sdk.api.session.threads * This class defines the state of a thread notification. */ enum class ThreadNotificationState { - - // There are no new message + /** + * There are no new message. + */ NO_NEW_MESSAGE, - // There is at least one new message + /** + * There is at least one new message. + */ NEW_MESSAGE, - // The is at least one new message that should be highlighted - // ex. "Hello @aris.kotsomitopoulos" + /** + * The is at least one new message that should be highlighted. + * ex. "Hello @aris.kotsomitopoulos" + */ NEW_HIGHLIGHTED_MESSAGE; } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/widgets/WidgetService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/widgets/WidgetService.kt index 8ad6500d25..c2094f46bd 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/widgets/WidgetService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/widgets/WidgetService.kt @@ -17,7 +17,7 @@ package org.matrix.android.sdk.api.session.widgets import androidx.lifecycle.LiveData -import org.matrix.android.sdk.api.query.QueryStringValue +import org.matrix.android.sdk.api.query.QueryStateEventValue import org.matrix.android.sdk.api.session.events.model.Content import org.matrix.android.sdk.api.session.widgets.model.Widget @@ -49,7 +49,7 @@ interface WidgetService { */ fun getRoomWidgets( roomId: String, - widgetId: QueryStringValue = QueryStringValue.NoCondition, + widgetId: QueryStateEventValue, widgetTypes: Set? = null, excludedTypes: Set? = null ): List @@ -70,7 +70,7 @@ interface WidgetService { */ fun getRoomWidgetsLive( roomId: String, - widgetId: QueryStringValue = QueryStringValue.NoCondition, + widgetId: QueryStateEventValue, widgetTypes: Set? = null, excludedTypes: Set? = null ): LiveData> diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/util/system/BuildVersionSdkIntProvider.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/util/BuildVersionSdkIntProvider.kt similarity index 87% rename from matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/util/system/BuildVersionSdkIntProvider.kt rename to matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/util/BuildVersionSdkIntProvider.kt index 515656049a..b7ea187ec5 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/util/system/BuildVersionSdkIntProvider.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/util/BuildVersionSdkIntProvider.kt @@ -14,9 +14,9 @@ * limitations under the License. */ -package org.matrix.android.sdk.internal.util.system +package org.matrix.android.sdk.api.util -internal interface BuildVersionSdkIntProvider { +interface BuildVersionSdkIntProvider { /** * Return the current version of the Android SDK. */ 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/api/util/DefaultBuildVersionSdkIntProvider.kt similarity index 85% rename from matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/util/system/DefaultBuildVersionSdkIntProvider.kt rename to matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/util/DefaultBuildVersionSdkIntProvider.kt index 806c6e9735..7f0024cafa 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/api/util/DefaultBuildVersionSdkIntProvider.kt @@ -14,12 +14,12 @@ * limitations under the License. */ -package org.matrix.android.sdk.internal.util.system +package org.matrix.android.sdk.api.util import android.os.Build import javax.inject.Inject -internal class DefaultBuildVersionSdkIntProvider @Inject constructor() : +class DefaultBuildVersionSdkIntProvider @Inject constructor() : BuildVersionSdkIntProvider { override fun get() = Build.VERSION.SDK_INT } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/util/MatrixItem.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/util/MatrixItem.kt index 8a29d00380..26dd31dc2d 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/util/MatrixItem.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/util/MatrixItem.kt @@ -33,9 +33,11 @@ sealed class MatrixItem( open val displayName: String?, open val avatarUrl: String? ) { - data class UserItem(override val id: String, - override val displayName: String? = null, - override val avatarUrl: String? = null) : + data class UserItem( + override val id: String, + override val displayName: String? = null, + override val avatarUrl: String? = null + ) : MatrixItem(id, displayName?.removeSuffix(IRC_PATTERN), avatarUrl) { init { @@ -45,10 +47,12 @@ sealed class MatrixItem( override fun updateAvatar(newAvatar: String?) = copy(avatarUrl = newAvatar) } - data class EveryoneInRoomItem(override val id: String, - override val displayName: String = NOTIFY_EVERYONE, - override val avatarUrl: String? = null, - val roomDisplayName: String? = null) : + data class EveryoneInRoomItem( + override val id: String, + override val displayName: String = NOTIFY_EVERYONE, + override val avatarUrl: String? = null, + val roomDisplayName: String? = null + ) : MatrixItem(id, displayName, avatarUrl) { init { if (BuildConfig.DEBUG) checkId() @@ -57,9 +61,11 @@ sealed class MatrixItem( override fun updateAvatar(newAvatar: String?) = copy(avatarUrl = newAvatar) } - data class EventItem(override val id: String, - override val displayName: String? = null, - override val avatarUrl: String? = null) : + data class EventItem( + override val id: String, + override val displayName: String? = null, + override val avatarUrl: String? = null + ) : MatrixItem(id, displayName, avatarUrl) { init { if (BuildConfig.DEBUG) checkId() @@ -68,9 +74,11 @@ sealed class MatrixItem( override fun updateAvatar(newAvatar: String?) = copy(avatarUrl = newAvatar) } - data class RoomItem(override val id: String, - override val displayName: String? = null, - override val avatarUrl: String? = null) : + data class RoomItem( + override val id: String, + override val displayName: String? = null, + override val avatarUrl: String? = null + ) : MatrixItem(id, displayName, avatarUrl) { init { if (BuildConfig.DEBUG) checkId() @@ -79,9 +87,11 @@ sealed class MatrixItem( override fun updateAvatar(newAvatar: String?) = copy(avatarUrl = newAvatar) } - data class SpaceItem(override val id: String, - override val displayName: String? = null, - override val avatarUrl: String? = null) : + data class SpaceItem( + override val id: String, + override val displayName: String? = null, + override val avatarUrl: String? = null + ) : MatrixItem(id, displayName, avatarUrl) { init { if (BuildConfig.DEBUG) checkId() @@ -90,9 +100,11 @@ sealed class MatrixItem( override fun updateAvatar(newAvatar: String?) = copy(avatarUrl = newAvatar) } - data class RoomAliasItem(override val id: String, - override val displayName: String? = null, - override val avatarUrl: String? = null) : + data class RoomAliasItem( + override val id: String, + override val displayName: String? = null, + override val avatarUrl: String? = null + ) : MatrixItem(id, displayName, avatarUrl) { init { if (BuildConfig.DEBUG) checkId() @@ -101,9 +113,11 @@ sealed class MatrixItem( override fun updateAvatar(newAvatar: String?) = copy(avatarUrl = newAvatar) } - data class GroupItem(override val id: String, - override val displayName: String? = null, - override val avatarUrl: String? = null) : + data class GroupItem( + override val id: String, + override val displayName: String? = null, + override val avatarUrl: String? = null + ) : MatrixItem(id, displayName, avatarUrl) { init { if (BuildConfig.DEBUG) checkId() @@ -124,20 +138,20 @@ sealed class MatrixItem( * Return the prefix as defined in the matrix spec (and not extracted from the id). */ private fun getIdPrefix() = when (this) { - is UserItem -> '@' - is EventItem -> '$' + is UserItem -> '@' + is EventItem -> '$' is SpaceItem, is RoomItem, is EveryoneInRoomItem -> '!' - is RoomAliasItem -> '#' - is GroupItem -> '+' + is RoomAliasItem -> '#' + is GroupItem -> '+' } fun firstLetterOfDisplayName(): String { val displayName = when (this) { // use the room display name for the notify everyone item is EveryoneInRoomItem -> roomDisplayName - else -> displayName + else -> displayName } return (displayName?.takeIf { it.isNotBlank() } ?: id) .let { dn -> diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/SessionManager.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/SessionManager.kt index 934d61de45..5f5bb1f951 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/SessionManager.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/SessionManager.kt @@ -27,8 +27,10 @@ import org.matrix.android.sdk.internal.session.SessionComponent import javax.inject.Inject @MatrixScope -internal class SessionManager @Inject constructor(private val matrixComponent: MatrixComponent, - private val sessionParamsStore: SessionParamsStore) { +internal class SessionManager @Inject constructor( + private val matrixComponent: MatrixComponent, + private val sessionParamsStore: SessionParamsStore +) { // SessionId -> SessionComponent private val sessionComponents = HashMap() @@ -38,6 +40,13 @@ internal class SessionManager @Inject constructor(private val matrixComponent: M return getOrCreateSessionComponent(sessionParams) } + fun getLastSession(): Session? { + val sessionParams = sessionParamsStore.getLast() + return sessionParams?.let { + getOrCreateSession(it) + } + } + fun getOrCreateSession(sessionParams: SessionParams): Session { return getOrCreateSessionComponent(sessionParams).session() } @@ -53,7 +62,7 @@ internal class SessionManager @Inject constructor(private val matrixComponent: M fun stopSession(sessionId: String) { val sessionComponent = sessionComponents[sessionId] ?: throw RuntimeException("You don't have a session for id $sessionId") - sessionComponent.session().stopSync() + sessionComponent.session().syncService().stopSync() } fun getOrCreateSessionComponent(sessionParams: SessionParams): SessionComponent { 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 46fa63334c..ea4502824e 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 @@ -98,15 +98,19 @@ internal interface AuthAPI { * https://github.com/matrix-org/matrix-doc/pull/2290 */ @POST(NetworkConstants.URI_API_PREFIX_PATH_R0 + "register/{threePid}/requestToken") - suspend fun add3Pid(@Path("threePid") threePid: String, - @Body params: AddThreePidRegistrationParams): AddThreePidRegistrationResponse + suspend fun add3Pid( + @Path("threePid") threePid: String, + @Body params: AddThreePidRegistrationParams + ): AddThreePidRegistrationResponse /** * Validate 3pid. */ @POST - suspend fun validate3Pid(@Url url: String, - @Body params: ValidationCodeBody): SuccessResult + suspend fun validate3Pid( + @Url url: String, + @Body params: ValidationCodeBody + ): SuccessResult /** * Get the supported login flow. diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/AuthModule.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/AuthModule.kt index 298e116199..ddb70be906 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/AuthModule.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/AuthModule.kt @@ -46,9 +46,11 @@ internal abstract class AuthModule { @JvmStatic @Provides @AuthDatabase - fun providesRealmConfiguration(context: Context, - realmKeysUtils: RealmKeysUtils, - authRealmMigration: AuthRealmMigration): RealmConfiguration { + fun providesRealmConfiguration( + context: Context, + realmKeysUtils: RealmKeysUtils, + authRealmMigration: AuthRealmMigration + ): RealmConfiguration { val old = File(context.filesDir, "matrix-sdk-auth") if (old.exists()) { old.renameTo(File(context.filesDir, "matrix-sdk-auth.realm")) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/DefaultAuthenticationService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/DefaultAuthenticationService.kt index 02dfce04b5..9d6b018a67 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/DefaultAuthenticationService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/DefaultAuthenticationService.kt @@ -40,6 +40,7 @@ import org.matrix.android.sdk.internal.auth.login.DefaultLoginWizard import org.matrix.android.sdk.internal.auth.login.DirectLoginTask import org.matrix.android.sdk.internal.auth.registration.DefaultRegistrationWizard import org.matrix.android.sdk.internal.auth.version.Versions +import org.matrix.android.sdk.internal.auth.version.doesServerSupportLogoutDevices import org.matrix.android.sdk.internal.auth.version.isLoginAndRegistrationSupportedBySdk import org.matrix.android.sdk.internal.auth.version.isSupportedBySdk import org.matrix.android.sdk.internal.di.Unauthenticated @@ -73,10 +74,7 @@ internal class DefaultAuthenticationService @Inject constructor( } override fun getLastAuthenticatedSession(): Session? { - val sessionParams = sessionParamsStore.getLast() - return sessionParams?.let { - sessionManager.getOrCreateSession(it) - } + return sessionManager.getLastSession() } override suspend fun getLoginFlowOfSession(sessionId: String): LoginFlowResult { @@ -130,16 +128,7 @@ internal class DefaultAuthenticationService @Inject constructor( ?.trim { it == '/' } } - /** - * This is the entry point of the authentication service. - * homeServerConnectionConfig contains a homeserver URL probably entered by the user, which can be a - * valid homeserver API url, the url of Element Web, or anything else. - */ override suspend fun getLoginFlow(homeServerConnectionConfig: HomeServerConnectionConfig): LoginFlowResult { - pendingSessionData = null - - pendingSessionStore.delete() - val result = runCatching { getLoginFlowInternal(homeServerConnectionConfig) } @@ -290,7 +279,7 @@ internal class DefaultAuthenticationService @Inject constructor( getLoginFlowResult(newAuthAPI, versions, wellknownResult.homeServerUrl) } - else -> throw Failure.OtherServerError("", HttpsURLConnection.HTTP_NOT_FOUND /* 404 */) + else -> throw Failure.OtherServerError("", HttpsURLConnection.HTTP_NOT_FOUND /* 404 */) } } @@ -304,7 +293,8 @@ internal class DefaultAuthenticationService @Inject constructor( ssoIdentityProviders = loginFlowResponse.flows.orEmpty().firstOrNull { it.type == LoginFlowTypes.SSO }?.ssoIdentityProvider, isLoginAndRegistrationSupported = versions.isLoginAndRegistrationSupportedBySdk(), homeServerUrl = homeServerUrl, - isOutdatedHomeserver = !versions.isSupportedBySdk() + isOutdatedHomeserver = !versions.isSupportedBySdk(), + isLogoutDevicesSupported = versions.doesServerSupportLogoutDevices() ) } @@ -323,8 +313,7 @@ internal class DefaultAuthenticationService @Inject constructor( } } - override val isRegistrationStarted: Boolean - get() = currentRegistrationWizard?.isRegistrationStarted == true + override fun isRegistrationStarted() = currentRegistrationWizard?.isRegistrationStarted() == true override fun getLoginWizard(): LoginWizard { return currentLoginWizard @@ -368,13 +357,17 @@ internal class DefaultAuthenticationService @Inject constructor( pendingSessionStore.delete() } - override suspend fun createSessionFromSso(homeServerConnectionConfig: HomeServerConnectionConfig, - credentials: Credentials): Session { + override suspend fun createSessionFromSso( + homeServerConnectionConfig: HomeServerConnectionConfig, + credentials: Credentials + ): Session { return sessionCreator.createSession(credentials, homeServerConnectionConfig) } - override suspend fun getWellKnownData(matrixId: String, - homeServerConnectionConfig: HomeServerConnectionConfig?): WellknownResult { + override suspend fun getWellKnownData( + matrixId: String, + homeServerConnectionConfig: HomeServerConnectionConfig? + ): WellknownResult { if (!MatrixPatterns.isUserId(matrixId)) { throw MatrixIdFailure.InvalidMatrixId } @@ -392,11 +385,13 @@ internal class DefaultAuthenticationService @Inject constructor( .withHomeServerUri("https://dummy.org") .build() - override suspend fun directAuthentication(homeServerConnectionConfig: HomeServerConnectionConfig, - matrixId: String, - password: String, - initialDeviceName: String, - deviceId: String?): Session { + override suspend fun directAuthentication( + homeServerConnectionConfig: HomeServerConnectionConfig, + matrixId: String, + password: String, + initialDeviceName: String, + deviceId: String? + ): Session { return directLoginTask.execute( DirectLoginTask.Params( homeServerConnectionConfig = homeServerConnectionConfig, diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/data/PasswordLoginParams.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/data/PasswordLoginParams.kt index 5be480f633..5f0a2298cb 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/data/PasswordLoginParams.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/data/PasswordLoginParams.kt @@ -31,7 +31,8 @@ internal data class PasswordLoginParams( @Json(name = "password") val password: String, @Json(name = "type") override val type: String, @Json(name = "initial_device_display_name") val deviceDisplayName: String?, - @Json(name = "device_id") val deviceId: String?) : LoginParams { + @Json(name = "device_id") val deviceId: String? +) : LoginParams { companion object { private const val IDENTIFIER_KEY_TYPE = "type" @@ -47,10 +48,12 @@ internal data class PasswordLoginParams( private const val IDENTIFIER_KEY_COUNTRY = "country" private const val IDENTIFIER_KEY_PHONE = "phone" - fun userIdentifier(user: String, - password: String, - deviceDisplayName: String?, - deviceId: String?): PasswordLoginParams { + fun userIdentifier( + user: String, + password: String, + deviceDisplayName: String?, + deviceId: String? + ): PasswordLoginParams { return PasswordLoginParams( identifier = mapOf( IDENTIFIER_KEY_TYPE to IDENTIFIER_KEY_TYPE_USER, @@ -63,11 +66,13 @@ internal data class PasswordLoginParams( ) } - fun thirdPartyIdentifier(medium: String, - address: String, - password: String, - deviceDisplayName: String?, - deviceId: String?): PasswordLoginParams { + fun thirdPartyIdentifier( + medium: String, + address: String, + password: String, + deviceDisplayName: String?, + deviceId: String? + ): PasswordLoginParams { return PasswordLoginParams( identifier = mapOf( IDENTIFIER_KEY_TYPE to IDENTIFIER_KEY_TYPE_THIRD_PARTY, @@ -81,11 +86,13 @@ internal data class PasswordLoginParams( ) } - fun phoneIdentifier(country: String, - phone: String, - password: String, - deviceDisplayName: String?, - deviceId: String?): PasswordLoginParams { + fun phoneIdentifier( + country: String, + phone: String, + password: String, + deviceDisplayName: String?, + deviceId: String? + ): PasswordLoginParams { return PasswordLoginParams( identifier = mapOf( IDENTIFIER_KEY_TYPE to IDENTIFIER_KEY_TYPE_PHONE, diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/db/RealmPendingSessionStore.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/db/RealmPendingSessionStore.kt index 13f26e321d..aae8ff8419 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/db/RealmPendingSessionStore.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/db/RealmPendingSessionStore.kt @@ -23,9 +23,10 @@ import org.matrix.android.sdk.internal.database.awaitTransaction import org.matrix.android.sdk.internal.di.AuthDatabase import javax.inject.Inject -internal class RealmPendingSessionStore @Inject constructor(private val mapper: PendingSessionMapper, - @AuthDatabase - private val realmConfiguration: RealmConfiguration +internal class RealmPendingSessionStore @Inject constructor( + private val mapper: PendingSessionMapper, + @AuthDatabase + private val realmConfiguration: RealmConfiguration ) : PendingSessionStore { override suspend fun savePendingSessionData(pendingSessionData: PendingSessionData) { diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/db/RealmSessionParamsStore.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/db/RealmSessionParamsStore.kt index 235ef6b709..4c3e3ca824 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/db/RealmSessionParamsStore.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/db/RealmSessionParamsStore.kt @@ -28,9 +28,10 @@ import org.matrix.android.sdk.internal.di.AuthDatabase import timber.log.Timber import javax.inject.Inject -internal class RealmSessionParamsStore @Inject constructor(private val mapper: SessionParamsMapper, - @AuthDatabase - private val realmConfiguration: RealmConfiguration +internal class RealmSessionParamsStore @Inject constructor( + private val mapper: SessionParamsMapper, + @AuthDatabase + private val realmConfiguration: RealmConfiguration ) : SessionParamsStore { override fun getLast(): SessionParams? { diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/login/DefaultLoginWizard.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/login/DefaultLoginWizard.kt index 0583951138..656a4f671b 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/login/DefaultLoginWizard.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/login/DefaultLoginWizard.kt @@ -52,10 +52,12 @@ internal class DefaultLoginWizard( return getProfileTask.execute(GetProfileTask.Params(matrixId)) } - override suspend fun login(login: String, - password: String, - initialDeviceName: String, - deviceId: String?): Session { + override suspend fun login( + login: String, + password: String, + initialDeviceName: String, + deviceId: String? + ): Session { val loginParams = if (Patterns.EMAIL_ADDRESS.matcher(login).matches()) { PasswordLoginParams.thirdPartyIdentifier( medium = ThreePidMedium.EMAIL, @@ -101,7 +103,7 @@ internal class DefaultLoginWizard( return sessionCreator.createSession(credentials, pendingSessionData.homeServerConnectionConfig) } - override suspend fun resetPassword(email: String, newPassword: String) { + override suspend fun resetPassword(email: String) { val param = RegisterAddThreePidTask.Params( RegisterThreePid.Email(email), pendingSessionData.clientSecret, @@ -115,18 +117,17 @@ internal class DefaultLoginWizard( authAPI.resetPassword(AddThreePidRegistrationParams.from(param)) } - pendingSessionData = pendingSessionData.copy(resetPasswordData = ResetPasswordData(newPassword, result)) + pendingSessionData = pendingSessionData.copy(resetPasswordData = ResetPasswordData(result)) .also { pendingSessionStore.savePendingSessionData(it) } } - override suspend fun resetPasswordMailConfirmed() { - val safeResetPasswordData = pendingSessionData.resetPasswordData - ?: throw IllegalStateException("developer error, no reset password in progress") - + override suspend fun resetPasswordMailConfirmed(newPassword: String, logoutAllDevices: Boolean) { + val resetPasswordData = pendingSessionData.resetPasswordData ?: throw IllegalStateException("Developer error - Must call resetPassword first") val param = ResetPasswordMailConfirmed.create( pendingSessionData.clientSecret, - safeResetPasswordData.addThreePidRegistrationResponse.sid, - safeResetPasswordData.newPassword + resetPasswordData.addThreePidRegistrationResponse.sid, + newPassword, + logoutAllDevices ) executeRequest(null) { diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/login/DirectLoginTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/login/DirectLoginTask.kt index 28706c7e80..c9311867c8 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/login/DirectLoginTask.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/login/DirectLoginTask.kt @@ -73,7 +73,7 @@ internal class DefaultDirectLoginTask @Inject constructor( homeServerUrl, throwable.fingerprint ) - else -> throwable + else -> throwable } } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/login/ResetPasswordData.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/login/ResetPasswordData.kt index a65ec38d6d..87a7b346dc 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/login/ResetPasswordData.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/login/ResetPasswordData.kt @@ -24,6 +24,5 @@ import org.matrix.android.sdk.internal.auth.registration.AddThreePidRegistration */ @JsonClass(generateAdapter = true) internal data class ResetPasswordData( - val newPassword: String, val addThreePidRegistrationResponse: AddThreePidRegistrationResponse ) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/login/ResetPasswordMailConfirmed.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/login/ResetPasswordMailConfirmed.kt index 4e0c000f87..01481f70dc 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/login/ResetPasswordMailConfirmed.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/login/ResetPasswordMailConfirmed.kt @@ -30,13 +30,17 @@ internal data class ResetPasswordMailConfirmed( // the new password @Json(name = "new_password") - val newPassword: String? = null + val newPassword: String? = null, + + @Json(name = "logout_devices") + val logoutDevices: Boolean? = null ) { companion object { - fun create(clientSecret: String, sid: String, newPassword: String): ResetPasswordMailConfirmed { + fun create(clientSecret: String, sid: String, newPassword: String, logoutDevices: Boolean?): ResetPasswordMailConfirmed { return ResetPasswordMailConfirmed( auth = AuthParams.createForResetPassword(clientSecret, sid), - newPassword = newPassword + newPassword = newPassword, + logoutDevices = logoutDevices ) } } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/registration/AddThreePidRegistrationParams.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/registration/AddThreePidRegistrationParams.kt index 2f05864d3b..f0cb6ebd6d 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/registration/AddThreePidRegistrationParams.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/registration/AddThreePidRegistrationParams.kt @@ -84,7 +84,7 @@ internal data class AddThreePidRegistrationParams( companion object { fun from(params: RegisterAddThreePidTask.Params): AddThreePidRegistrationParams { return when (params.threePid) { - is RegisterThreePid.Email -> AddThreePidRegistrationParams( + is RegisterThreePid.Email -> AddThreePidRegistrationParams( email = params.threePid.email, clientSecret = params.clientSecret, sendAttempt = params.sendAttempt 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 8f00f3440c..d6ec0297b4 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 @@ -49,29 +49,29 @@ internal class DefaultRegistrationWizard( private val validateCodeTask: ValidateCodeTask = DefaultValidateCodeTask(authAPI) private val registerCustomTask: RegisterCustomTask = DefaultRegisterCustomTask(authAPI) - override val currentThreePid: String? - get() { - return when (val threePid = pendingSessionData.currentThreePidData?.threePid) { - is RegisterThreePid.Email -> threePid.email - is RegisterThreePid.Msisdn -> { - // Take formatted msisdn if provided by the server - pendingSessionData.currentThreePidData?.addThreePidRegistrationResponse?.formattedMsisdn?.takeIf { it.isNotBlank() } ?: threePid.msisdn - } - null -> null + override fun getCurrentThreePid(): String? { + return when (val threePid = pendingSessionData.currentThreePidData?.threePid) { + is RegisterThreePid.Email -> threePid.email + is RegisterThreePid.Msisdn -> { + // Take formatted msisdn if provided by the server + pendingSessionData.currentThreePidData?.addThreePidRegistrationResponse?.formattedMsisdn?.takeIf { it.isNotBlank() } ?: threePid.msisdn } + null -> null } + } - override val isRegistrationStarted: Boolean - get() = pendingSessionData.isRegistrationStarted + override fun isRegistrationStarted() = pendingSessionData.isRegistrationStarted override suspend fun getRegistrationFlow(): RegistrationResult { val params = RegistrationParams() return performRegistrationRequest(params) } - override suspend fun createAccount(userName: String?, - password: String?, - initialDeviceDisplayName: String?): RegistrationResult { + override suspend fun createAccount( + userName: String?, + password: String?, + initialDeviceDisplayName: String? + ): RegistrationResult { val params = RegistrationParams( username = userName, password = password, diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/registration/RegisterAddThreePidTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/registration/RegisterAddThreePidTask.kt index 54a8ba0e6c..b84ed61361 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/registration/RegisterAddThreePidTask.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/registration/RegisterAddThreePidTask.kt @@ -41,7 +41,7 @@ internal class DefaultRegisterAddThreePidTask( private fun RegisterThreePid.toPath(): String { return when (this) { - is RegisterThreePid.Email -> "email" + is RegisterThreePid.Email -> "email" is RegisterThreePid.Msisdn -> "msisdn" } } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/registration/ThreePidData.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/registration/ThreePidData.kt index c8f71af306..dde0a44ea0 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/registration/ThreePidData.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/registration/ThreePidData.kt @@ -40,11 +40,13 @@ internal data class ThreePidData( } companion object { - fun from(threePid: RegisterThreePid, - addThreePidRegistrationResponse: AddThreePidRegistrationResponse, - registrationParams: RegistrationParams): ThreePidData { + fun from( + threePid: RegisterThreePid, + addThreePidRegistrationResponse: AddThreePidRegistrationResponse, + registrationParams: RegistrationParams + ): ThreePidData { return when (threePid) { - is RegisterThreePid.Email -> + is RegisterThreePid.Email -> ThreePidData(threePid.email, "", "", addThreePidRegistrationResponse, registrationParams) is RegisterThreePid.Msisdn -> ThreePidData("", threePid.msisdn, threePid.countryCode, addThreePidRegistrationResponse, registrationParams) 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 9b135c347d..8118f9faff 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 @@ -35,9 +35,11 @@ import kotlin.coroutines.suspendCoroutine * @return UiaResult if UIA handled, failed or cancelled * */ -internal suspend fun handleUIA(failure: Throwable, - interceptor: UserInteractiveAuthInterceptor, - retryBlock: suspend (UIABaseAuth) -> Unit): UiaResult { +internal suspend fun handleUIA( + failure: Throwable, + interceptor: UserInteractiveAuthInterceptor, + retryBlock: suspend (UIABaseAuth) -> Unit +): UiaResult { Timber.d("## UIA: check error ${failure.message}") val flowResponse = failure.toRegistrationFlowResponse() ?: return UiaResult.FAILURE.also { diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/version/HomeServerVersion.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/version/HomeServerVersion.kt index 815f8de2de..75639c6a21 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/version/HomeServerVersion.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/version/HomeServerVersion.kt @@ -16,6 +16,8 @@ package org.matrix.android.sdk.internal.auth.version +import org.matrix.android.sdk.api.extensions.ensureNotEmpty + /** * Values will take the form "rX.Y.Z". * Ref: https://matrix.org/docs/spec/client_server/latest#get-matrix-client-versions @@ -33,19 +35,19 @@ internal data class HomeServerVersion( minor < other.minor -> -1 patch > other.patch -> 1 patch < other.patch -> -1 - else -> 0 + else -> 0 } } companion object { - internal val pattern = Regex("""[r|v](\d+)\.(\d+)\.(\d+)""") + internal val pattern = Regex("""[r|v](\d+)\.(\d+)(?:\.(\d+))?""") internal fun parse(value: String): HomeServerVersion? { val result = pattern.matchEntire(value) ?: return null return HomeServerVersion( major = result.groupValues[1].toInt(), minor = result.groupValues[2].toInt(), - patch = result.groupValues[3].toInt() + patch = result.groupValues.getOrNull(index = 3)?.ensureNotEmpty()?.toInt() ?: 0 ) } @@ -56,6 +58,7 @@ internal data class HomeServerVersion( val r0_4_0 = HomeServerVersion(major = 0, minor = 4, patch = 0) val r0_5_0 = HomeServerVersion(major = 0, minor = 5, patch = 0) val r0_6_0 = HomeServerVersion(major = 0, minor = 6, patch = 0) + val r0_6_1 = HomeServerVersion(major = 0, minor = 6, patch = 1) val v1_3_0 = HomeServerVersion(major = 1, minor = 3, patch = 0) } } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/version/Versions.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/version/Versions.kt index cee4b12138..915b25134b 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/version/Versions.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/version/Versions.kt @@ -111,6 +111,15 @@ private fun Versions.doesServerSeparatesAddAndBind(): Boolean { unstableFeatures?.get(FEATURE_SEPARATE_ADD_AND_BIND) ?: false } +/** + * Indicate if the server supports MSC2457 `logout_devices` parameter when setting a new password. + * + * @return true if logout_devices is supported + */ +internal fun Versions.doesServerSupportLogoutDevices(): Boolean { + return getMaxVersion() >= HomeServerVersion.r0_6_1 +} + private fun Versions.getMaxVersion(): HomeServerVersion { return supportedVersions ?.mapNotNull { HomeServerVersion.parse(it) } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/CryptoModule.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/CryptoModule.kt index 2265526484..c69a859016 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/CryptoModule.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/CryptoModule.kt @@ -110,10 +110,12 @@ internal abstract class CryptoModule { @Provides @CryptoDatabase @SessionScope - fun providesRealmConfiguration(@SessionFilesDirectory directory: File, - @UserMd5 userMd5: String, - realmKeysUtils: RealmKeysUtils, - realmCryptoStoreMigration: RealmCryptoStoreMigration): RealmConfiguration { + fun providesRealmConfiguration( + @SessionFilesDirectory directory: File, + @UserMd5 userMd5: String, + realmKeysUtils: RealmKeysUtils, + realmCryptoStoreMigration: RealmCryptoStoreMigration + ): RealmConfiguration { return RealmConfiguration.Builder() .directory(directory) .apply { 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 824478f1d3..e0bcde2296 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 @@ -188,8 +188,8 @@ internal class DefaultCryptoService @Inject constructor( fun onStateEvent(roomId: String, event: Event) { when (event.type) { - EventType.STATE_ROOM_ENCRYPTION -> onRoomEncryptionEvent(roomId, event) - EventType.STATE_ROOM_MEMBER -> onRoomMembershipEvent(roomId, event) + EventType.STATE_ROOM_ENCRYPTION -> onRoomEncryptionEvent(roomId, event) + EventType.STATE_ROOM_MEMBER -> onRoomMembershipEvent(roomId, event) EventType.STATE_ROOM_HISTORY_VISIBILITY -> onRoomHistoryVisibilityEvent(roomId, event) } } @@ -198,8 +198,8 @@ internal class DefaultCryptoService @Inject constructor( // handle state events if (event.isStateEvent()) { when (event.type) { - EventType.STATE_ROOM_ENCRYPTION -> onRoomEncryptionEvent(roomId, event) - EventType.STATE_ROOM_MEMBER -> onRoomMembershipEvent(roomId, event) + EventType.STATE_ROOM_ENCRYPTION -> onRoomEncryptionEvent(roomId, event) + EventType.STATE_ROOM_MEMBER -> onRoomMembershipEvent(roomId, event) EventType.STATE_ROOM_HISTORY_VISIBILITY -> onRoomHistoryVisibilityEvent(roomId, event) } } @@ -592,10 +592,12 @@ internal class DefaultCryptoService @Inject constructor( * @param membersId list of members to start tracking their devices * @return true if the operation succeeds. */ - private suspend fun setEncryptionInRoom(roomId: String, - algorithm: String?, - inhibitDeviceQuery: Boolean, - membersId: List): Boolean { + private suspend fun setEncryptionInRoom( + roomId: String, + algorithm: String?, + inhibitDeviceQuery: Boolean, + membersId: List + ): Boolean { // If we already have encryption in this room, we should ignore this event // (for now at least. Maybe we should alert the user somehow?) val existingAlgorithm = cryptoStore.getRoomAlgorithm(roomId) @@ -618,8 +620,8 @@ internal class DefaultCryptoService @Inject constructor( val alg: IMXEncrypting? = when (algorithm) { MXCRYPTO_ALGORITHM_MEGOLM -> megolmEncryptionFactory.create(roomId) - MXCRYPTO_ALGORITHM_OLM -> olmEncryptionFactory.create(roomId) - else -> null + MXCRYPTO_ALGORITHM_OLM -> olmEncryptionFactory.create(roomId) + else -> null } if (alg != null) { @@ -691,10 +693,12 @@ internal class DefaultCryptoService @Inject constructor( * @param roomId the room identifier the event will be sent. * @param callback the asynchronous callback */ - override fun encryptEventContent(eventContent: Content, - eventType: String, - roomId: String, - callback: MatrixCallback) { + override fun encryptEventContent( + eventContent: Content, + eventType: String, + roomId: String, + callback: MatrixCallback + ) { // moved to crypto scope to have uptodate values cryptoCoroutineScope.launch(coroutineDispatchers.crypto) { val userIds = getRoomUserIds(roomId) @@ -796,10 +800,10 @@ internal class DefaultCryptoService @Inject constructor( // Keys are imported directly, not waiting for end of sync onRoomKeyEvent(event) } - EventType.REQUEST_SECRET -> { + EventType.REQUEST_SECRET -> { secretShareManager.handleSecretRequest(event) } - EventType.ROOM_KEY_REQUEST -> { + EventType.ROOM_KEY_REQUEST -> { event.getClearContent().toModel()?.let { req -> // We'll always get these because we send room key requests to // '*' (ie. 'all devices') which includes the sending device, @@ -811,13 +815,13 @@ internal class DefaultCryptoService @Inject constructor( } } } - EventType.SEND_SECRET -> { + EventType.SEND_SECRET -> { onSecretSendReceived(event) } - EventType.ROOM_KEY_WITHHELD -> { + EventType.ROOM_KEY_WITHHELD -> { onKeyWithHeldReceived(event) } - else -> { + else -> { // ignore } } @@ -879,10 +883,12 @@ internal class DefaultCryptoService @Inject constructor( /** * Returns true if handled by SDK, otherwise should be sent to application layer. */ - private fun handleSDKLevelGossip(secretName: String?, - secretValue: String): Boolean { + private fun handleSDKLevelGossip( + secretName: String?, + secretValue: String + ): Boolean { return when (secretName) { - MASTER_KEY_SSSS_NAME -> { + MASTER_KEY_SSSS_NAME -> { crossSigningService.onSecretMSKGossip(secretValue) true } @@ -898,7 +904,7 @@ internal class DefaultCryptoService @Inject constructor( keysBackupService.onSecretKeyGossip(secretValue) true } - else -> false + else -> false } } @@ -1022,9 +1028,11 @@ internal class DefaultCryptoService @Inject constructor( * @param progressListener the progress listener * @return the result ImportRoomKeysResult */ - override suspend fun importRoomKeys(roomKeysAsArray: ByteArray, - password: String, - progressListener: ProgressListener?): ImportRoomKeysResult { + override suspend fun importRoomKeys( + roomKeysAsArray: ByteArray, + password: String, + progressListener: ProgressListener? + ): ImportRoomKeysResult { return withContext(coroutineDispatchers.crypto) { Timber.tag(loggerTag.value).v("importRoomKeys starts") @@ -1290,10 +1298,6 @@ internal class DefaultCryptoService @Inject constructor( return cryptoStore.getWithHeldMegolmSession(roomId, sessionId) } - override fun logDbUsageInfo() { - cryptoStore.logDbUsageInfo() - } - override fun prepareToEncrypt(roomId: String, callback: MatrixCallback) { cryptoCoroutineScope.launch(coroutineDispatchers.crypto) { Timber.tag(loggerTag.value).d("prepareToEncrypt() roomId:$roomId Check room members up to date") 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 18b815b3d8..4f3900adb9 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 @@ -231,8 +231,8 @@ internal class DeviceListManager @Inject constructor( for ((k, value) in failures) { val statusCode = when (val status = value["status"]) { is Double -> status.toInt() - is Int -> status.toInt() - else -> 0 + is Int -> status.toInt() + else -> 0 } if (statusCode == 503) { synchronized(notReadyToRetryHS) { 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 cb61bbe1de..c1d04eb22b 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 @@ -29,6 +29,7 @@ import org.matrix.android.sdk.api.session.crypto.model.MXUsersDevicesMap import org.matrix.android.sdk.api.session.events.model.Event import org.matrix.android.sdk.api.session.events.model.EventType import org.matrix.android.sdk.api.session.events.model.content.OlmEventContent +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.internal.crypto.actions.EnsureOlmSessionsForDevicesAction import org.matrix.android.sdk.internal.crypto.actions.MessageEncrypter @@ -42,7 +43,7 @@ import javax.inject.Inject private const val SEND_TO_DEVICE_RETRY_COUNT = 3 -private val loggerTag = LoggerTag("CryptoSyncHandler", LoggerTag.CRYPTO) +private val loggerTag = LoggerTag("EventDecryptor", LoggerTag.CRYPTO) @SessionScope internal class EventDecryptor @Inject constructor( @@ -110,6 +111,16 @@ internal class EventDecryptor @Inject constructor( if (eventContent == null) { Timber.tag(loggerTag.value).e("decryptEvent : empty event content") throw MXCryptoError.Base(MXCryptoError.ErrorType.BAD_ENCRYPTED_MESSAGE, MXCryptoError.BAD_ENCRYPTED_MESSAGE_REASON) + } else if (event.isRedacted()) { + // we shouldn't attempt to decrypt a redacted event because the content is cleared and decryption will fail because of null algorithm + return MXEventDecryptionResult( + clearEvent = mapOf( + "room_id" to event.roomId.orEmpty(), + "type" to EventType.MESSAGE, + "content" to emptyMap(), + "unsigned" to event.unsignedData.toContent() + ) + ) } else { val algorithm = eventContent["algorithm"]?.toString() val alg = roomDecryptorProvider.getOrCreateRoomDecryptor(event.roomId, algorithm) 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 28ddf291b2..e4d322cadd 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 @@ -44,7 +44,8 @@ private val loggerTag = LoggerTag("InboundGroupSessionStore", LoggerTag.CRYPTO) internal class InboundGroupSessionStore @Inject constructor( private val store: IMXCryptoStore, private val cryptoCoroutineScope: CoroutineScope, - private val coroutineDispatchers: MatrixCoroutineDispatchers) { + private val coroutineDispatchers: MatrixCoroutineDispatchers +) { private data class CacheKey( val sessionId: String, diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/IncomingKeyRequestManager.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/IncomingKeyRequestManager.kt index 13f2fb861a..7f36224dae 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/IncomingKeyRequestManager.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/IncomingKeyRequestManager.kt @@ -135,7 +135,7 @@ internal class IncomingKeyRequestManager @Inject constructor( // just add to the buffer incomingRequestBuffer.add(validMegolmRequest) } - MegolmRequestAction.Cancel -> { + MegolmRequestAction.Cancel -> { // ignore, we can't cancel as it's not known (probably already processed) // still notify app layer if it was passed up previously IncomingRoomKeyRequest.fromRestRequest(senderId, request, clock)?.let { iReq -> @@ -159,7 +159,7 @@ internal class IncomingKeyRequestManager @Inject constructor( MegolmRequestAction.Request -> { // it's already in buffer, nop keep existing } - MegolmRequestAction.Cancel -> { + MegolmRequestAction.Cancel -> { // discard the request in buffer incomingRequestBuffer.remove(existing) outgoingRequestScope.launch(coroutineDispatchers.computation) { @@ -369,9 +369,11 @@ internal class IncomingKeyRequestManager @Inject constructor( shareMegolmKey(validReq, requestingDevice, null) } - private suspend fun shareMegolmKey(validRequest: ValidMegolmRequestBody, - requestingDevice: CryptoDeviceInfo, - chainIndex: Long?): Boolean { + private suspend fun shareMegolmKey( + validRequest: ValidMegolmRequestBody, + requestingDevice: CryptoDeviceInfo, + chainIndex: Long? + ): Boolean { Timber.tag(loggerTag.value) .d("try to re-share Megolm Key at index $chainIndex for ${validRequest.shortDbgString()}") diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/MXCryptoAlgorithms.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/MXCryptoAlgorithms.kt index 5a5ee9e696..14b0e8e125 100755 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/MXCryptoAlgorithms.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/MXCryptoAlgorithms.kt @@ -32,7 +32,7 @@ internal object MXCryptoAlgorithms { return when (algorithm) { MXCRYPTO_ALGORITHM_MEGOLM, MXCRYPTO_ALGORITHM_OLM -> true - else -> false + else -> false } } @@ -47,7 +47,7 @@ internal object MXCryptoAlgorithms { return when (algorithm) { MXCRYPTO_ALGORITHM_MEGOLM, MXCRYPTO_ALGORITHM_OLM -> true - else -> false + else -> false } } 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 6c18f29307..24b6fd166f 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 @@ -96,8 +96,9 @@ internal class MXOlmDevice @Inject constructor( // So, store these message indexes per timeline id. // // The first level keys are timeline ids. - // The second level keys are strings of form "||" - private val inboundGroupSessionMessageIndexes: MutableMap> = HashMap() + // The second level values is a Map that represents: + // "|||" --> eventId + private val inboundGroupSessionMessageIndexes: MutableMap> = HashMap() init { // Retrieve the account from the store @@ -599,13 +600,15 @@ internal class MXOlmDevice @Inject constructor( * @param exportFormat true if the megolm keys are in export format * @return true if the operation succeeds. */ - fun addInboundGroupSession(sessionId: String, - sessionKey: String, - roomId: String, - senderKey: String, - forwardingCurve25519KeyChain: List, - keysClaimed: Map, - exportFormat: Boolean): AddSessionResult { + fun addInboundGroupSession( + sessionId: String, + sessionKey: String, + roomId: String, + senderKey: String, + forwardingCurve25519KeyChain: List, + keysClaimed: Map, + exportFormat: Boolean + ): AddSessionResult { val candidateSession = OlmInboundGroupSessionWrapper2(sessionKey, exportFormat) val existingSessionHolder = tryOrNull { getInboundGroupSession(sessionId, senderKey, roomId) } val existingSession = existingSessionHolder?.wrapper @@ -755,67 +758,74 @@ internal class MXOlmDevice @Inject constructor( * @param body the base64-encoded body of the encrypted message. * @param roomId the room in which the message was received. * @param timeline the id of the timeline where the event is decrypted. It is used to prevent replay attack. + * @param eventId the eventId of the message that will be decrypted * @param sessionId the session identifier. * @param senderKey the base64-encoded curve25519 key of the sender. - * @return the decrypting result. Nil if the sessionId is unknown. + * @return the decrypting result. Null if the sessionId is unknown. */ @Throws(MXCryptoError::class) - suspend fun decryptGroupMessage(body: String, - roomId: String, - timeline: String?, - sessionId: String, - senderKey: String): OlmDecryptionResult { + suspend fun decryptGroupMessage( + body: String, + roomId: String, + timeline: String?, + eventId: String, + sessionId: String, + senderKey: String + ): OlmDecryptionResult { val sessionHolder = getInboundGroupSession(sessionId, senderKey, roomId) val wrapper = sessionHolder.wrapper val inboundGroupSession = wrapper.olmInboundGroupSession ?: throw MXCryptoError.Base(MXCryptoError.ErrorType.UNABLE_TO_DECRYPT, "Session is null") - // Check that the room id matches the original one for the session. This stops - // the HS pretending a message was targeting a different room. - if (roomId == wrapper.roomId) { - val decryptResult = try { - sessionHolder.mutex.withLock { - inboundGroupSession.decryptMessage(body) - } - } catch (e: OlmException) { - Timber.tag(loggerTag.value).e(e, "## decryptGroupMessage () : decryptMessage failed") - throw MXCryptoError.OlmError(e) - } - - if (timeline?.isNotBlank() == true) { - val timelineSet = inboundGroupSessionMessageIndexes.getOrPut(timeline) { mutableSetOf() } - - val messageIndexKey = senderKey + "|" + sessionId + "|" + decryptResult.mIndex - - if (timelineSet.contains(messageIndexKey)) { - val reason = String.format(MXCryptoError.DUPLICATE_MESSAGE_INDEX_REASON, decryptResult.mIndex) - Timber.tag(loggerTag.value).e("## decryptGroupMessage() timelineId=$timeline: $reason") - throw MXCryptoError.Base(MXCryptoError.ErrorType.DUPLICATED_MESSAGE_INDEX, reason) - } - - timelineSet.add(messageIndexKey) - } - - inboundGroupSessionStore.storeInBoundGroupSession(sessionHolder, sessionId, senderKey) - val payload = try { - val adapter = MoshiProvider.providesMoshi().adapter(JSON_DICT_PARAMETERIZED_TYPE) - val payloadString = convertFromUTF8(decryptResult.mDecryptedMessage) - adapter.fromJson(payloadString) - } catch (e: Exception) { - Timber.tag(loggerTag.value).e("## decryptGroupMessage() : fails to parse the payload") - throw MXCryptoError.Base(MXCryptoError.ErrorType.BAD_DECRYPTED_FORMAT, MXCryptoError.BAD_DECRYPTED_FORMAT_TEXT_REASON) - } - - return OlmDecryptionResult( - payload, - wrapper.keysClaimed, - senderKey, - wrapper.forwardingCurve25519KeyChain - ) - } else { + if (roomId != wrapper.roomId) { + // Check that the room id matches the original one for the session. This stops + // the HS pretending a message was targeting a different room. val reason = String.format(MXCryptoError.INBOUND_SESSION_MISMATCH_ROOM_ID_REASON, roomId, wrapper.roomId) Timber.tag(loggerTag.value).e("## decryptGroupMessage() : $reason") throw MXCryptoError.Base(MXCryptoError.ErrorType.INBOUND_SESSION_MISMATCH_ROOM_ID, reason) } + val decryptResult = try { + sessionHolder.mutex.withLock { + inboundGroupSession.decryptMessage(body) + } + } catch (e: OlmException) { + Timber.tag(loggerTag.value).e(e, "## decryptGroupMessage () : decryptMessage failed") + throw MXCryptoError.OlmError(e) + } + + val messageIndexKey = senderKey + "|" + sessionId + "|" + roomId + "|" + decryptResult.mIndex + Timber.tag(loggerTag.value).v("##########################################################") + Timber.tag(loggerTag.value).v("## decryptGroupMessage() timeline: $timeline") + Timber.tag(loggerTag.value).v("## decryptGroupMessage() senderKey: $senderKey") + Timber.tag(loggerTag.value).v("## decryptGroupMessage() sessionId: $sessionId") + Timber.tag(loggerTag.value).v("## decryptGroupMessage() roomId: $roomId") + Timber.tag(loggerTag.value).v("## decryptGroupMessage() eventId: $eventId") + Timber.tag(loggerTag.value).v("## decryptGroupMessage() mIndex: ${decryptResult.mIndex}") + + if (timeline?.isNotBlank() == true) { + val replayAttackMap = inboundGroupSessionMessageIndexes.getOrPut(timeline) { mutableMapOf() } + if (replayAttackMap.contains(messageIndexKey) && replayAttackMap[messageIndexKey] != eventId) { + val reason = String.format(MXCryptoError.DUPLICATE_MESSAGE_INDEX_REASON, decryptResult.mIndex) + Timber.tag(loggerTag.value).e("## decryptGroupMessage() timelineId=$timeline: $reason") + throw MXCryptoError.Base(MXCryptoError.ErrorType.DUPLICATED_MESSAGE_INDEX, reason) + } + replayAttackMap[messageIndexKey] = eventId + } + inboundGroupSessionStore.storeInBoundGroupSession(sessionHolder, sessionId, senderKey) + val payload = try { + val adapter = MoshiProvider.providesMoshi().adapter(JSON_DICT_PARAMETERIZED_TYPE) + val payloadString = convertFromUTF8(decryptResult.mDecryptedMessage) + adapter.fromJson(payloadString) + } catch (e: Exception) { + Timber.tag(loggerTag.value).e("## decryptGroupMessage() : fails to parse the payload") + throw MXCryptoError.Base(MXCryptoError.ErrorType.BAD_DECRYPTED_FORMAT, MXCryptoError.BAD_DECRYPTED_FORMAT_TEXT_REASON) + } + + return OlmDecryptionResult( + payload, + wrapper.keysClaimed, + senderKey, + wrapper.forwardingCurve25519KeyChain + ) } /** diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/ObjectSigner.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/ObjectSigner.kt index ab562d954a..3f4b633ea0 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/ObjectSigner.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/ObjectSigner.kt @@ -19,8 +19,10 @@ package org.matrix.android.sdk.internal.crypto import org.matrix.android.sdk.api.auth.data.Credentials import javax.inject.Inject -internal class ObjectSigner @Inject constructor(private val credentials: Credentials, - private val olmDevice: MXOlmDevice) { +internal class ObjectSigner @Inject constructor( + private val credentials: Credentials, + private val olmDevice: MXOlmDevice +) { /** * Sign Object. diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/OutgoingKeyRequestManager.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/OutgoingKeyRequestManager.kt index d7652d0771..6b22cc09d6 100755 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/OutgoingKeyRequestManager.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/OutgoingKeyRequestManager.kt @@ -71,7 +71,8 @@ internal class OutgoingKeyRequestManager @Inject constructor( private val inboundGroupSessionStore: InboundGroupSessionStore, private val sendToDeviceTask: SendToDeviceTask, private val deviceListManager: DeviceListManager, - private val perSessionBackupQueryRateLimiter: PerSessionBackupQueryRateLimiter) { + private val perSessionBackupQueryRateLimiter: PerSessionBackupQueryRateLimiter +) { private val dispatcher = Executors.newSingleThreadExecutor().asCoroutineDispatcher() private val outgoingRequestScope = CoroutineScope(SupervisorJob() + dispatcher) @@ -180,13 +181,15 @@ internal class OutgoingKeyRequestManager @Inject constructor( } } - fun onRoomKeyForwarded(sessionId: String, - algorithm: String, - roomId: String, - senderKey: String, - fromDevice: String?, - fromIndex: Int, - event: Event) { + fun onRoomKeyForwarded( + sessionId: String, + algorithm: String, + roomId: String, + senderKey: String, + fromDevice: String?, + fromIndex: Int, + event: Event + ) { Timber.tag(loggerTag.value).d("Key forwarded for $sessionId from ${event.senderId}|$fromDevice at index $fromIndex") outgoingRequestScope.launch { sequencer.post { @@ -208,12 +211,14 @@ internal class OutgoingKeyRequestManager @Inject constructor( } } - fun onRoomKeyWithHeld(sessionId: String, - algorithm: String, - roomId: String, - senderKey: String, - fromDevice: String?, - event: Event) { + fun onRoomKeyWithHeld( + sessionId: String, + algorithm: String, + roomId: String, + senderKey: String, + fromDevice: String?, + event: Event + ) { outgoingRequestScope.launch { sequencer.post { Timber.tag(loggerTag.value).d("Withheld received for $sessionId from ${event.senderId}|$fromDevice") @@ -294,19 +299,19 @@ internal class OutgoingKeyRequestManager @Inject constructor( } knownRequest.forEach { request -> when (request.state) { - OutgoingRoomKeyRequestState.UNSENT -> { + OutgoingRoomKeyRequestState.UNSENT -> { if (request.fromIndex >= localKnownChainIndex) { // we have a good index we can cancel cryptoStore.deleteOutgoingRoomKeyRequest(request.requestId) } } - OutgoingRoomKeyRequestState.SENT -> { + OutgoingRoomKeyRequestState.SENT -> { // It was already sent, and index satisfied we can cancel if (request.fromIndex >= localKnownChainIndex) { cryptoStore.updateOutgoingRoomKeyRequestState(request.requestId, OutgoingRoomKeyRequestState.CANCELLATION_PENDING) } } - OutgoingRoomKeyRequestState.CANCELLATION_PENDING -> { + OutgoingRoomKeyRequestState.CANCELLATION_PENDING -> { // It is already marked to be cancelled } OutgoingRoomKeyRequestState.CANCELLATION_PENDING_AND_WILL_RESEND -> { @@ -315,7 +320,7 @@ internal class OutgoingKeyRequestManager @Inject constructor( cryptoStore.updateOutgoingRoomKeyRequestState(request.requestId, OutgoingRoomKeyRequestState.CANCELLATION_PENDING) } } - OutgoingRoomKeyRequestState.SENT_THEN_CANCELED -> { + OutgoingRoomKeyRequestState.SENT_THEN_CANCELED -> { // was already canceled // if we need a better index, should we resend? } @@ -346,14 +351,14 @@ internal class OutgoingKeyRequestManager @Inject constructor( val existing = cryptoStore.getOutgoingRoomKeyRequest(requestBody) Timber.tag(loggerTag.value).v("Queueing key request exiting is ${existing?.state}") when (existing?.state) { - null -> { + null -> { // create a new one cryptoStore.getOrAddOutgoingRoomKeyRequest(requestBody, recipients, fromIndex) } - OutgoingRoomKeyRequestState.UNSENT -> { + OutgoingRoomKeyRequestState.UNSENT -> { // nothing it's new or not yet handled } - OutgoingRoomKeyRequestState.SENT -> { + OutgoingRoomKeyRequestState.SENT -> { // it was already requested Timber.tag(loggerTag.value).d("The session ${requestBody.sessionId} is already requested") if (force) { @@ -366,7 +371,7 @@ internal class OutgoingKeyRequestManager @Inject constructor( } } } - OutgoingRoomKeyRequestState.CANCELLATION_PENDING -> { + OutgoingRoomKeyRequestState.CANCELLATION_PENDING -> { // request is canceled only if I got the keys so what to do here... if (force) { cryptoStore.updateOutgoingRoomKeyRequestState(existing.requestId, OutgoingRoomKeyRequestState.CANCELLATION_PENDING_AND_WILL_RESEND) @@ -375,7 +380,7 @@ internal class OutgoingKeyRequestManager @Inject constructor( OutgoingRoomKeyRequestState.CANCELLATION_PENDING_AND_WILL_RESEND -> { // It's already going to resend } - OutgoingRoomKeyRequestState.SENT_THEN_CANCELED -> { + OutgoingRoomKeyRequestState.SENT_THEN_CANCELED -> { if (force) { cryptoStore.deleteOutgoingRoomKeyRequest(existing.requestId) cryptoStore.getOrAddOutgoingRoomKeyRequest(requestBody, recipients, fromIndex) @@ -396,11 +401,11 @@ internal class OutgoingKeyRequestManager @Inject constructor( measureTimeMillis { toProcess.forEach { when (it.state) { - OutgoingRoomKeyRequestState.UNSENT -> handleUnsentRequest(it) - OutgoingRoomKeyRequestState.CANCELLATION_PENDING -> handleRequestToCancel(it) + OutgoingRoomKeyRequestState.UNSENT -> handleUnsentRequest(it) + OutgoingRoomKeyRequestState.CANCELLATION_PENDING -> handleRequestToCancel(it) OutgoingRoomKeyRequestState.CANCELLATION_PENDING_AND_WILL_RESEND -> handleRequestToCancelWillResend(it) OutgoingRoomKeyRequestState.SENT_THEN_CANCELED, - OutgoingRoomKeyRequestState.SENT -> { + OutgoingRoomKeyRequestState.SENT -> { // these are filtered out } } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/RoomDecryptorProvider.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/RoomDecryptorProvider.kt index a80bafbe79..d37e60d289 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/RoomDecryptorProvider.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/RoomDecryptorProvider.kt @@ -85,7 +85,7 @@ internal class RoomDecryptorProvider @Inject constructor( } } } - else -> olmDecryptionFactory.create() + else -> olmDecryptionFactory.create() } if (!roomId.isNullOrEmpty()) { synchronized(roomDecryptors) { diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/RoomEncryptorsStore.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/RoomEncryptorsStore.kt index 1a8c160d9c..9f6714cc45 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/RoomEncryptorsStore.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/RoomEncryptorsStore.kt @@ -49,8 +49,8 @@ internal class RoomEncryptorsStore @Inject constructor( } else { val alg: IMXEncrypting? = when (cryptoStore.getRoomAlgorithm(roomId)) { MXCRYPTO_ALGORITHM_MEGOLM -> megolmEncryptionFactory.create(roomId) - MXCRYPTO_ALGORITHM_OLM -> olmEncryptionFactory.create(roomId) - else -> null + MXCRYPTO_ALGORITHM_OLM -> olmEncryptionFactory.create(roomId) + else -> null } alg?.let { roomEncryptors.put(roomId, it) } return@synchronized alg diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/SecretShareManager.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/SecretShareManager.kt index 6fb6914206..a79e1a8901 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/SecretShareManager.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/SecretShareManager.kt @@ -150,14 +150,14 @@ internal class SecretShareManager @Inject constructor( // we can share the secret val secretValue = 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 ?.let { extractCurveKeyFromRecoveryKey(it)?.toBase64NoPadding() } - else -> null + else -> null } if (secretValue == null) { Timber.tag(loggerTag.value) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/actions/EnsureOlmSessionsForDevicesAction.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/actions/EnsureOlmSessionsForDevicesAction.kt index c728f1b682..c263192fee 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/actions/EnsureOlmSessionsForDevicesAction.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/actions/EnsureOlmSessionsForDevicesAction.kt @@ -39,7 +39,8 @@ private val loggerTag = LoggerTag("EnsureOlmSessionsForDevicesAction", LoggerTag internal class EnsureOlmSessionsForDevicesAction @Inject constructor( private val olmDevice: MXOlmDevice, private val coroutineDispatchers: MatrixCoroutineDispatchers, - private val oneTimeKeysForUsersDeviceTask: ClaimOneTimeKeysForUsersDeviceTask) { + private val oneTimeKeysForUsersDeviceTask: ClaimOneTimeKeysForUsersDeviceTask +) { private val ensureMutex = Mutex() diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/actions/EnsureOlmSessionsForUsersAction.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/actions/EnsureOlmSessionsForUsersAction.kt index 4c5720daf2..da09524668 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/actions/EnsureOlmSessionsForUsersAction.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/actions/EnsureOlmSessionsForUsersAction.kt @@ -23,9 +23,11 @@ import org.matrix.android.sdk.internal.crypto.store.IMXCryptoStore import timber.log.Timber import javax.inject.Inject -internal class EnsureOlmSessionsForUsersAction @Inject constructor(private val olmDevice: MXOlmDevice, - private val cryptoStore: IMXCryptoStore, - private val ensureOlmSessionsForDevicesAction: EnsureOlmSessionsForDevicesAction) { +internal class EnsureOlmSessionsForUsersAction @Inject constructor( + private val olmDevice: MXOlmDevice, + private val cryptoStore: IMXCryptoStore, + private val ensureOlmSessionsForDevicesAction: EnsureOlmSessionsForDevicesAction +) { /** * Try to make sure we have established olm sessions for the given users. 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 67d73c21ed..f6ab96aee6 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 @@ -33,11 +33,12 @@ import javax.inject.Inject private val loggerTag = LoggerTag("MegolmSessionDataImporter", LoggerTag.CRYPTO) -internal class MegolmSessionDataImporter @Inject constructor(private val olmDevice: MXOlmDevice, - private val roomDecryptorProvider: RoomDecryptorProvider, - private val outgoingKeyRequestManager: OutgoingKeyRequestManager, - private val cryptoStore: IMXCryptoStore, - private val clock: Clock, +internal class MegolmSessionDataImporter @Inject constructor( + private val olmDevice: MXOlmDevice, + private val roomDecryptorProvider: RoomDecryptorProvider, + private val outgoingKeyRequestManager: OutgoingKeyRequestManager, + private val cryptoStore: IMXCryptoStore, + private val clock: Clock, ) { /** @@ -50,9 +51,11 @@ internal class MegolmSessionDataImporter @Inject constructor(private val olmDevi * @return import room keys result */ @WorkerThread - fun handle(megolmSessionsData: List, - fromBackup: Boolean, - progressListener: ProgressListener?): ImportRoomKeysResult { + fun handle( + megolmSessionsData: List, + fromBackup: Boolean, + progressListener: ProgressListener? + ): ImportRoomKeysResult { val t0 = clock.epochMillis() val totalNumbersOfKeys = megolmSessionsData.size diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/actions/MessageEncrypter.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/actions/MessageEncrypter.kt index 919e38c391..eff2132820 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/actions/MessageEncrypter.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/actions/MessageEncrypter.kt @@ -36,7 +36,8 @@ internal class MessageEncrypter @Inject constructor( private val userId: String, @DeviceId private val deviceId: String?, - private val olmDevice: MXOlmDevice) { + private val olmDevice: MXOlmDevice +) { /** * Encrypt an event payload for a list of devices. * This method must be called from the getCryptoHandler() thread. diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/actions/SetDeviceVerificationAction.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/actions/SetDeviceVerificationAction.kt index 60181138fb..6028b1a5a2 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/actions/SetDeviceVerificationAction.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/actions/SetDeviceVerificationAction.kt @@ -26,7 +26,8 @@ import javax.inject.Inject internal class SetDeviceVerificationAction @Inject constructor( private val cryptoStore: IMXCryptoStore, @UserId private val userId: String, - private val defaultKeysBackupService: DefaultKeysBackupService) { + private val defaultKeysBackupService: DefaultKeysBackupService +) { fun handle(trustLevel: DeviceTrustLevel, userId: String, deviceId: String) { val device = cryptoStore.getUserDevice(userId, deviceId) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/algorithms/IMXGroupEncryption.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/algorithms/IMXGroupEncryption.kt index 8cf01f1972..9ec78f37cf 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/algorithms/IMXGroupEncryption.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/algorithms/IMXGroupEncryption.kt @@ -45,8 +45,10 @@ internal interface IMXGroupEncryption { * * @return true in case of success */ - suspend fun reshareKey(groupSessionId: String, - userId: String, - deviceId: String, - senderKey: String): Boolean + suspend fun reshareKey( + groupSessionId: String, + userId: String, + deviceId: String, + senderKey: String + ): Boolean } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/algorithms/megolm/MXMegolmDecryption.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/algorithms/megolm/MXMegolmDecryption.kt index d15b18ec79..141d6f74cd 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/algorithms/megolm/MXMegolmDecryption.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/algorithms/megolm/MXMegolmDecryption.kt @@ -78,6 +78,7 @@ internal class MXMegolmDecryption( encryptedEventContent.ciphertext, event.roomId, timeline, + eventId = event.eventId.orEmpty(), encryptedEventContent.sessionId, encryptedEventContent.senderKey ) @@ -148,7 +149,8 @@ internal class MXMegolmDecryption( throw MXCryptoError.Base( MXCryptoError.ErrorType.KEYS_WITHHELD, withHeldInfo.code?.value ?: "", - withHeldInfo.reason) + withHeldInfo.reason + ) } if (requestKeysOnFail) { @@ -248,9 +250,9 @@ internal class MXMegolmDecryption( ) when (addSessionResult) { - is MXOlmDevice.AddSessionResult.Imported -> addSessionResult.ratchetIndex + is MXOlmDevice.AddSessionResult.Imported -> addSessionResult.ratchetIndex is MXOlmDevice.AddSessionResult.NotImportedHigherIndex -> addSessionResult.newIndex - else -> null + else -> null }?.let { index -> if (event.getClearType() == EventType.FORWARDED_ROOM_KEY) { val fromDevice = (event.content?.get("sender_key") as? String)?.let { senderDeviceIdentityKey -> @@ -267,7 +269,8 @@ internal class MXMegolmDecryption( senderKey = senderKey, fromIndex = index, fromDevice = fromDevice, - event = event) + event = event + ) cryptoStore.saveIncomingForwardKeyAuditTrail( roomId = roomKeyContent.roomId, @@ -276,7 +279,8 @@ internal class MXMegolmDecryption( algorithm = roomKeyContent.algorithm ?: "", userId = event.senderId ?: "", deviceId = fromDevice ?: "", - chainIndex = index.toLong()) + chainIndex = index.toLong() + ) // The index is used to decide if we cancel sent request or if we wait for a better key outgoingKeyRequestManager.postCancelRequestForSessionIfNeeded(roomKeyContent.sessionId, roomKeyContent.roomId, senderKey, index) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/algorithms/megolm/MXMegolmDecryptionFactory.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/algorithms/megolm/MXMegolmDecryptionFactory.kt index 096773a959..81a6fb28c0 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/algorithms/megolm/MXMegolmDecryptionFactory.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/algorithms/megolm/MXMegolmDecryptionFactory.kt @@ -35,6 +35,7 @@ internal class MXMegolmDecryptionFactory @Inject constructor( olmDevice, outgoingKeyRequestManager, cryptoStore, - eventsManager) + eventsManager + ) } } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/algorithms/megolm/MXMegolmEncryption.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/algorithms/megolm/MXMegolmEncryption.kt index 8b4e9df607..7bfbae6edf 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 @@ -84,9 +84,11 @@ internal class MXMegolmEncryption( private var sessionRotationPeriodMsgs: Int = 100 private var sessionRotationPeriodMs: Int = 7 * 24 * 3600 * 1000 - override suspend fun encryptEventContent(eventContent: Content, - eventType: String, - userIds: List): Content { + override suspend fun encryptEventContent( + eventContent: Content, + eventType: String, + userIds: List + ): Content { val ts = clock.epochMillis() Timber.tag(loggerTag.value).v("encryptEventContent : getDevicesInRoom") val devices = getDevicesInRoom(userIds) @@ -201,8 +203,10 @@ internal class MXMegolmEncryption( * @param session the session info * @param devicesByUsers the devices map */ - private suspend fun shareKey(session: MXOutboundSessionInfo, - devicesByUsers: Map>) { + private suspend fun shareKey( + session: MXOutboundSessionInfo, + devicesByUsers: Map> + ) { // nothing to send, the task is done if (devicesByUsers.isEmpty()) { Timber.tag(loggerTag.value).v("shareKey() : nothing more to do") @@ -230,8 +234,10 @@ internal class MXMegolmEncryption( * @param session the session info * @param devicesByUser the devices map */ - private suspend fun shareUserDevicesKey(session: MXOutboundSessionInfo, - devicesByUser: Map>) { + private suspend fun shareUserDevicesKey( + session: MXOutboundSessionInfo, + devicesByUser: Map> + ) { val sessionKey = olmDevice.getSessionKey(session.sessionId) val chainIndex = olmDevice.getMessageIndex(session.sessionId) @@ -321,10 +327,12 @@ internal class MXMegolmEncryption( } } - private suspend fun notifyKeyWithHeld(targets: List, - sessionId: String, - senderKey: String?, - code: WithHeldCode) { + private suspend fun notifyKeyWithHeld( + targets: List, + sessionId: String, + senderKey: String?, + code: WithHeldCode + ) { Timber.tag(loggerTag.value).d( "notifyKeyWithHeld() :sending withheld for session:$sessionId and code $code to" + " ${targets.joinToString { "${it.userId}|${it.deviceId}" }}" @@ -435,10 +443,12 @@ internal class MXMegolmEncryption( } } - override suspend fun reshareKey(groupSessionId: String, - userId: String, - deviceId: String, - senderKey: String): Boolean { + override suspend fun reshareKey( + groupSessionId: String, + userId: String, + deviceId: String, + senderKey: String + ): Boolean { Timber.tag(loggerTag.value).i("process reshareKey for $groupSessionId to $userId:$deviceId") val deviceInfo = cryptoStore.getUserDevice(userId, deviceId) ?: return false .also { Timber.tag(loggerTag.value).w("reshareKey: Device not found") } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/algorithms/megolm/SharedWithHelper.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/algorithms/megolm/SharedWithHelper.kt index 61ad345c62..30fd403ce8 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/algorithms/megolm/SharedWithHelper.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/algorithms/megolm/SharedWithHelper.kt @@ -23,7 +23,8 @@ import org.matrix.android.sdk.internal.crypto.store.IMXCryptoStore internal class SharedWithHelper( private val roomId: String, private val sessionId: String, - private val cryptoStore: IMXCryptoStore) { + private val cryptoStore: IMXCryptoStore +) { fun sharedWithDevices(): MXUsersDevicesMap { return cryptoStore.getSharedWithInfo(roomId, sessionId) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/algorithms/olm/MXOlmDecryption.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/algorithms/olm/MXOlmDecryption.kt index 23c8f0e905..219cadac46 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/algorithms/olm/MXOlmDecryption.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/algorithms/olm/MXOlmDecryption.kt @@ -38,7 +38,8 @@ internal class MXOlmDecryption( // The olm device interface private val olmDevice: MXOlmDevice, // the matrix userId - private val userId: String) : + private val userId: String +) : IMXDecrypting { @Throws(MXCryptoError::class) @@ -190,9 +191,9 @@ internal class MXOlmDecryption( val messageBody = message["body"] as? String ?: return null val messageType = when (val typeAsVoid = message["type"]) { is Double -> typeAsVoid.toInt() - is Int -> typeAsVoid - is Long -> typeAsVoid.toInt() - else -> return null + is Int -> typeAsVoid + is Long -> typeAsVoid.toInt() + else -> return null } // Try each session in turn diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/algorithms/olm/MXOlmDecryptionFactory.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/algorithms/olm/MXOlmDecryptionFactory.kt index d5c5e85e41..a50ac8ca8a 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/algorithms/olm/MXOlmDecryptionFactory.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/algorithms/olm/MXOlmDecryptionFactory.kt @@ -20,8 +20,10 @@ import org.matrix.android.sdk.internal.crypto.MXOlmDevice import org.matrix.android.sdk.internal.di.UserId import javax.inject.Inject -internal class MXOlmDecryptionFactory @Inject constructor(private val olmDevice: MXOlmDevice, - @UserId private val userId: String) { +internal class MXOlmDecryptionFactory @Inject constructor( + private val olmDevice: MXOlmDevice, + @UserId private val userId: String +) { fun create(): MXOlmDecryption { return MXOlmDecryption( 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 bde1d65093..fb70e23b03 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 @@ -32,7 +32,8 @@ internal class MXOlmEncryption( private val cryptoStore: IMXCryptoStore, private val messageEncrypter: MessageEncrypter, private val deviceListManager: DeviceListManager, - private val ensureOlmSessionsForUsersAction: EnsureOlmSessionsForUsersAction) : + private val ensureOlmSessionsForUsersAction: EnsureOlmSessionsForUsersAction +) : IMXEncrypting { override suspend fun encryptEventContent(eventContent: Content, eventType: String, userIds: List): Content { diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/algorithms/olm/MXOlmEncryptionFactory.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/algorithms/olm/MXOlmEncryptionFactory.kt index 44e55900e4..012886203e 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/algorithms/olm/MXOlmEncryptionFactory.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/algorithms/olm/MXOlmEncryptionFactory.kt @@ -24,12 +24,14 @@ import org.matrix.android.sdk.internal.crypto.actions.MessageEncrypter import org.matrix.android.sdk.internal.crypto.store.IMXCryptoStore import javax.inject.Inject -internal class MXOlmEncryptionFactory @Inject constructor(private val olmDevice: MXOlmDevice, - private val cryptoStore: IMXCryptoStore, - private val messageEncrypter: MessageEncrypter, - private val deviceListManager: DeviceListManager, - private val coroutineDispatchers: MatrixCoroutineDispatchers, - private val ensureOlmSessionsForUsersAction: EnsureOlmSessionsForUsersAction) { +internal class MXOlmEncryptionFactory @Inject constructor( + private val olmDevice: MXOlmDevice, + private val cryptoStore: IMXCryptoStore, + private val messageEncrypter: MessageEncrypter, + private val deviceListManager: DeviceListManager, + private val coroutineDispatchers: MatrixCoroutineDispatchers, + private val ensureOlmSessionsForUsersAction: EnsureOlmSessionsForUsersAction +) { fun create(roomId: String): MXOlmEncryption { return MXOlmEncryption( diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/api/CryptoApi.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/api/CryptoApi.kt index f5ead35933..d5a8bdfd7c 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/api/CryptoApi.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/api/CryptoApi.kt @@ -117,9 +117,11 @@ internal interface CryptoApi { * @param body the body */ @PUT(NetworkConstants.URI_API_PREFIX_PATH_R0 + "sendToDevice/{eventType}/{txnId}") - suspend fun sendToDevice(@Path("eventType") eventType: String, - @Path("txnId") transactionId: String, - @Body body: SendToDeviceBody) + suspend fun sendToDevice( + @Path("eventType") eventType: String, + @Path("txnId") transactionId: String, + @Body body: SendToDeviceBody + ) /** * Delete a device. @@ -129,8 +131,10 @@ internal interface CryptoApi { * @param params the deletion parameters */ @HTTP(path = NetworkConstants.URI_API_PREFIX_PATH_R0 + "devices/{device_id}", method = "DELETE", hasBody = true) - suspend fun deleteDevice(@Path("device_id") deviceId: String, - @Body params: DeleteDeviceParams) + suspend fun deleteDevice( + @Path("device_id") deviceId: String, + @Body params: DeleteDeviceParams + ) /** * Update the device information. @@ -140,8 +144,10 @@ internal interface CryptoApi { * @param params the params */ @PUT(NetworkConstants.URI_API_PREFIX_PATH_R0 + "devices/{device_id}") - suspend fun updateDeviceInfo(@Path("device_id") deviceId: String, - @Body params: UpdateDeviceInfoBody) + suspend fun updateDeviceInfo( + @Path("device_id") deviceId: String, + @Body params: UpdateDeviceInfoBody + ) /** * Get the update devices list from two sync token. @@ -151,6 +157,8 @@ internal interface CryptoApi { * @param newToken the up-to token. */ @GET(NetworkConstants.URI_API_PREFIX_PATH_R0 + "keys/changes") - suspend fun getKeyChanges(@Query("from") oldToken: String, - @Query("to") newToken: String): KeyChangesResponse + suspend fun getKeyChanges( + @Query("from") oldToken: String, + @Query("to") newToken: String + ): KeyChangesResponse } 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 7ff08cd127..37c8e755ff 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 @@ -41,10 +41,12 @@ internal object MXEncryptedAttachments { private const val SECRET_KEY_SPEC_ALGORITHM = "AES" private const val MESSAGE_DIGEST_ALGORITHM = "SHA-256" - fun encrypt(clearStream: InputStream, - outputFile: File, - clock: Clock, - progress: ((current: Int, total: Int) -> Unit)): EncryptedFileInfo { + fun encrypt( + clearStream: InputStream, + outputFile: File, + clock: Clock, + progress: ((current: Int, total: Int) -> Unit) + ): EncryptedFileInfo { val t0 = clock.epochMillis() val secureRandom = SecureRandom() val initVectorBytes = ByteArray(16) { 0.toByte() } @@ -236,10 +238,11 @@ internal object MXEncryptedAttachments { * @param clock a clock to retrieve current time * @return true in case of success, false in case of error */ - fun decryptAttachment(attachmentStream: InputStream?, - elementToDecrypt: ElementToDecrypt?, - outputStream: OutputStream, - clock: Clock + fun decryptAttachment( + attachmentStream: InputStream?, + elementToDecrypt: ElementToDecrypt?, + outputStream: OutputStream, + clock: Clock ): Boolean { // sanity checks if (null == attachmentStream || elementToDecrypt == null) { diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/crosssigning/CrossSigningOlm.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/crosssigning/CrossSigningOlm.kt index 4fa355cd2a..3218b99948 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/crosssigning/CrossSigningOlm.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/crosssigning/CrossSigningOlm.kt @@ -53,13 +53,13 @@ internal class CrossSigningOlm @Inject constructor( fun signObject(type: KeyType, strToSign: String): Map { val myKeys = cryptoStore.getMyCrossSigningInfo() val pubKey = when (type) { - KeyType.SELF -> myKeys?.selfSigningKey() - KeyType.USER -> myKeys?.userKey() + KeyType.SELF -> myKeys?.selfSigningKey() + KeyType.USER -> myKeys?.userKey() KeyType.MASTER -> myKeys?.masterKey() }?.unpaddedBase64PublicKey val pkSigning = when (type) { - KeyType.SELF -> selfSigningPkSigning - KeyType.USER -> userPkSigning + KeyType.SELF -> selfSigningPkSigning + KeyType.USER -> userPkSigning KeyType.MASTER -> masterPkSigning } if (pubKey == null || pkSigning == null) { @@ -76,8 +76,8 @@ internal class CrossSigningOlm @Inject constructor( ?: throw NoSuchElementException("Cross Signing not configured") val myUserID = myKeys.userId val pubKey = when (type) { - KeyType.SELF -> myKeys.selfSigningKey() - KeyType.USER -> myKeys.userKey() + KeyType.SELF -> myKeys.selfSigningKey() + KeyType.USER -> myKeys.userKey() KeyType.MASTER -> myKeys.masterKey() }?.unpaddedBase64PublicKey ?: throw NoSuchElementException("Cross Signing not configured") val signaturesMadeByMyKey = signatures[myUserID] // Signatures made by me diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/crosssigning/DefaultCrossSigningService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/crosssigning/DefaultCrossSigningService.kt index 6c198abc2e..e466def1a1 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/crosssigning/DefaultCrossSigningService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/crosssigning/DefaultCrossSigningService.kt @@ -261,9 +261,10 @@ internal class DefaultCrossSigningService @Inject constructor( } } - override fun checkTrustFromPrivateKeys(masterKeyPrivateKey: String?, - uskKeyPrivateKey: String?, - sskPrivateKey: String? + override fun checkTrustFromPrivateKeys( + masterKeyPrivateKey: String?, + uskKeyPrivateKey: String?, + sskPrivateKey: String? ): UserTrustResult { val mxCrossSigningInfo = getMyCrossSigningKeys() ?: return UserTrustResult.CrossSigningNotConfigured(userId) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/crosssigning/UpdateTrustWorker.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/crosssigning/UpdateTrustWorker.kt index 74f0f5745d..f1dc060e10 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/crosssigning/UpdateTrustWorker.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/crosssigning/UpdateTrustWorker.kt @@ -149,7 +149,7 @@ internal class UpdateTrustWorker(context: Context, params: WorkerParameters, ses val trusts = otherInfos.mapValues { entry -> when (entry.key) { myUserId -> myTrustResult - else -> { + else -> { crossSigningService.checkOtherMSKTrusted(myCrossSigningInfo, entry.value).also { Timber.v("## CrossSigning - user:${entry.key} result:$it") } @@ -276,10 +276,12 @@ internal class UpdateTrustWorker(context: Context, params: WorkerParameters, ses } } - private fun computeRoomShield(myCrossSigningInfo: MXCrossSigningInfo?, - cryptoRealm: Realm, - activeMemberUserIds: List, - roomSummaryEntity: RoomSummaryEntity): RoomEncryptionTrustLevel { + private fun computeRoomShield( + myCrossSigningInfo: MXCrossSigningInfo?, + cryptoRealm: Realm, + activeMemberUserIds: List, + roomSummaryEntity: RoomSummaryEntity + ): RoomEncryptionTrustLevel { Timber.v("## CrossSigning - computeRoomShield ${roomSummaryEntity.roomId} -> ${activeMemberUserIds.logLimit()}") // The set of “all users” depends on the type of room: // For regular / topic rooms which have more than 2 members (including yourself) are considered when decorating a room diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/keysbackup/DefaultKeysBackupService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/keysbackup/DefaultKeysBackupService.kt index 813adf7459..5eaa106af3 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/keysbackup/DefaultKeysBackupService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/keysbackup/DefaultKeysBackupService.kt @@ -137,17 +137,11 @@ internal class DefaultKeysBackupService @Inject constructor( private var keysBackupStateListener: KeysBackupStateListener? = null - override val isEnabled: Boolean - get() = keysBackupStateManager.isEnabled + override fun isEnabled(): Boolean = keysBackupStateManager.isEnabled - override val isStucked: Boolean - get() = keysBackupStateManager.isStucked + override fun isStuck(): Boolean = keysBackupStateManager.isStuck - override val state: KeysBackupState - get() = keysBackupStateManager.state - - override val currentBackupVersion: String? - get() = keysBackupVersion?.version + override fun getState(): KeysBackupState = keysBackupStateManager.state override fun addListener(listener: KeysBackupStateListener) { keysBackupStateManager.addListener(listener) @@ -157,9 +151,11 @@ internal class DefaultKeysBackupService @Inject constructor( keysBackupStateManager.removeListener(listener) } - override fun prepareKeysBackupVersion(password: String?, - progressListener: ProgressListener?, - callback: MatrixCallback) { + override fun prepareKeysBackupVersion( + password: String?, + progressListener: ProgressListener?, + callback: MatrixCallback + ) { cryptoCoroutineScope.launch(coroutineDispatchers.io) { try { val olmPkDecryption = OlmPkDecryption() @@ -233,8 +229,10 @@ internal class DefaultKeysBackupService @Inject constructor( } } - override fun createKeysBackupVersion(keysBackupCreationInfo: MegolmBackupCreationInfo, - callback: MatrixCallback) { + override fun createKeysBackupVersion( + keysBackupCreationInfo: MegolmBackupCreationInfo, + callback: MatrixCallback + ) { @Suppress("UNCHECKED_CAST") val createKeysBackupVersionBody = CreateKeysBackupVersionBody( algorithm = keysBackupCreationInfo.algorithm, @@ -291,7 +289,7 @@ internal class DefaultKeysBackupService @Inject constructor( this.callback = object : MatrixCallback { private fun eventuallyRestartBackup() { // Do not stay in KeysBackupState.Unknown but check what is available on the homeserver - if (state == KeysBackupState.Unknown) { + if (getState() == KeysBackupState.Unknown) { checkAndStartKeysBackup() } } @@ -324,7 +322,7 @@ internal class DefaultKeysBackupService @Inject constructor( // val hashServer = keysBackupData?.backupLastServerHash return when { - totalNumberOfKeysLocally < totalNumberOfKeysServer -> { + totalNumberOfKeysLocally < totalNumberOfKeysServer -> { // Server contains more keys than this device true } @@ -333,7 +331,7 @@ internal class DefaultKeysBackupService @Inject constructor( // TODO We have not found any algorithm to determine if a restore is recommended here. Return false for the moment false } - else -> false + else -> false } } @@ -345,9 +343,11 @@ internal class DefaultKeysBackupService @Inject constructor( return cryptoStore.inboundGroupSessionsCount(true) } - override fun backupAllGroupSessions(progressListener: ProgressListener?, - callback: MatrixCallback?) { - if (!isEnabled || backupOlmPkEncryption == null || keysBackupVersion == null) { + override fun backupAllGroupSessions( + progressListener: ProgressListener?, + callback: MatrixCallback? + ) { + if (!isEnabled() || backupOlmPkEncryption == null || keysBackupVersion == null) { callback?.onFailure(Throwable("Backup not enabled")) return } @@ -383,7 +383,7 @@ internal class DefaultKeysBackupService @Inject constructor( } // If backup is finished, notify the main listener - if (state === KeysBackupState.ReadyToBackUp) { + if (getState() === KeysBackupState.ReadyToBackUp) { backupAllGroupSessionsCallback?.onSuccess(Unit) resetBackupAllGroupSessionsListeners() } @@ -397,8 +397,10 @@ internal class DefaultKeysBackupService @Inject constructor( }) } - override fun getKeysBackupTrust(keysBackupVersion: KeysVersionResult, - callback: MatrixCallback) { + override fun getKeysBackupTrust( + keysBackupVersion: KeysVersionResult, + callback: MatrixCallback + ) { // TODO Validate with François that this is correct object : Task { override suspend fun execute(params: KeysVersionResult): KeysBackupVersionTrust { @@ -505,9 +507,11 @@ internal class DefaultKeysBackupService @Inject constructor( ) } - override fun trustKeysBackupVersion(keysBackupVersion: KeysVersionResult, - trust: Boolean, - callback: MatrixCallback) { + override fun trustKeysBackupVersion( + keysBackupVersion: KeysVersionResult, + trust: Boolean, + callback: MatrixCallback + ) { Timber.v("trustKeyBackupVersion: $trust, version ${keysBackupVersion.version}") // Get auth data to update it @@ -589,9 +593,11 @@ internal class DefaultKeysBackupService @Inject constructor( } } - override fun trustKeysBackupVersionWithRecoveryKey(keysBackupVersion: KeysVersionResult, - recoveryKey: String, - callback: MatrixCallback) { + override fun trustKeysBackupVersionWithRecoveryKey( + keysBackupVersion: KeysVersionResult, + recoveryKey: String, + callback: MatrixCallback + ) { Timber.v("trustKeysBackupVersionWithRecoveryKey: version ${keysBackupVersion.version}") cryptoCoroutineScope.launch(coroutineDispatchers.io) { @@ -608,9 +614,11 @@ internal class DefaultKeysBackupService @Inject constructor( } } - override fun trustKeysBackupVersionWithPassphrase(keysBackupVersion: KeysVersionResult, - password: String, - callback: MatrixCallback) { + override fun trustKeysBackupVersionWithPassphrase( + keysBackupVersion: KeysVersionResult, + password: String, + callback: MatrixCallback + ) { Timber.v("trustKeysBackupVersionWithPassphrase: version ${keysBackupVersion.version}") cryptoCoroutineScope.launch(coroutineDispatchers.io) { @@ -628,7 +636,7 @@ internal class DefaultKeysBackupService @Inject constructor( } } - override fun onSecretKeyGossip(secret: String) { + fun onSecretKeyGossip(secret: String) { Timber.i("## CrossSigning - onSecretKeyGossip") cryptoCoroutineScope.launch(coroutineDispatchers.io) { @@ -707,12 +715,14 @@ internal class DefaultKeysBackupService @Inject constructor( progressListener.onProgress(backedUpKeys, total) } - override fun restoreKeysWithRecoveryKey(keysVersionResult: KeysVersionResult, - recoveryKey: String, - roomId: String?, - sessionId: String?, - stepProgressListener: StepProgressListener?, - callback: MatrixCallback) { + override fun restoreKeysWithRecoveryKey( + keysVersionResult: KeysVersionResult, + recoveryKey: String, + roomId: String?, + sessionId: String?, + stepProgressListener: StepProgressListener?, + callback: MatrixCallback + ) { Timber.v("restoreKeysWithRecoveryKey: From backup version: ${keysVersionResult.version}") cryptoCoroutineScope.launch(coroutineDispatchers.io) { @@ -806,12 +816,14 @@ internal class DefaultKeysBackupService @Inject constructor( } } - override fun restoreKeyBackupWithPassword(keysBackupVersion: KeysVersionResult, - password: String, - roomId: String?, - sessionId: String?, - stepProgressListener: StepProgressListener?, - callback: MatrixCallback) { + override fun restoreKeyBackupWithPassword( + keysBackupVersion: KeysVersionResult, + password: String, + roomId: String?, + sessionId: String?, + stepProgressListener: StepProgressListener?, + callback: MatrixCallback + ) { Timber.v("[MXKeyBackup] restoreKeyBackup with password: From backup version: ${keysBackupVersion.version}") cryptoCoroutineScope.launch(coroutineDispatchers.io) { @@ -859,9 +871,11 @@ internal class DefaultKeysBackupService @Inject constructor( * Same method as [RoomKeysRestClient.getRoomKey] except that it accepts nullable * parameters and always returns a KeysBackupData object through the Callback. */ - private suspend fun getKeys(sessionId: String?, - roomId: String?, - version: String): KeysBackupData { + private suspend fun getKeys( + sessionId: String?, + roomId: String?, + version: String + ): KeysBackupData { return if (roomId != null && sessionId != null) { // Get key for the room and for the session val data = getRoomSessionDataTask.execute(GetRoomSessionDataTask.Params(roomId, sessionId, version)) @@ -915,12 +929,12 @@ internal class DefaultKeysBackupService @Inject constructor( */ fun maybeBackupKeys() { when { - isStucked -> { + isStuck() -> { // If not already done, or in error case, check for a valid backup version on the homeserver. // If there is one, maybeBackupKeys will be called again. checkAndStartKeysBackup() } - state == KeysBackupState.ReadyToBackUp -> { + getState() == KeysBackupState.ReadyToBackUp -> { keysBackupStateManager.state = KeysBackupState.WillBackUp // Wait between 0 and 10 seconds, to avoid backup requests from @@ -933,14 +947,16 @@ internal class DefaultKeysBackupService @Inject constructor( uiHandler.post { backupKeys() } } } - else -> { - Timber.v("maybeBackupKeys: Skip it because state: $state") + else -> { + Timber.v("maybeBackupKeys: Skip it because state: ${getState()}") } } } - override fun getVersion(version: String, - callback: MatrixCallback) { + override fun getVersion( + version: String, + callback: MatrixCallback + ) { getKeysBackupVersionTask .configureWith(version) { this.callback = object : MatrixCallback { @@ -976,7 +992,7 @@ internal class DefaultKeysBackupService @Inject constructor( override fun onSuccess(data: KeysBackupLastVersionResult) { val localBackupVersion = keysBackupVersion?.version when (data) { - KeysBackupLastVersionResult.NoKeysBackup -> { + KeysBackupLastVersionResult.NoKeysBackup -> { if (localBackupVersion == null) { // No backup on the server, and backup is not active callback.onSuccess(true) @@ -1018,9 +1034,9 @@ internal class DefaultKeysBackupService @Inject constructor( } override fun checkAndStartKeysBackup() { - if (!isStucked) { + if (!isStuck()) { // Try to start or restart the backup only if it is in unknown or bad state - Timber.w("checkAndStartKeysBackup: invalid state: $state") + Timber.w("checkAndStartKeysBackup: invalid state: ${getState()}") return } @@ -1179,10 +1195,12 @@ internal class DefaultKeysBackupService @Inject constructor( } } - override fun computePrivateKey(passphrase: String, - privateKeySalt: String, - privateKeyIterations: Int, - progressListener: ProgressListener): ByteArray { + override fun computePrivateKey( + passphrase: String, + privateKeySalt: String, + privateKeyIterations: Int, + progressListener: ProgressListener + ): ByteArray { return deriveKey(passphrase, privateKeySalt, privateKeyIterations, progressListener) } @@ -1259,16 +1277,16 @@ internal class DefaultKeysBackupService @Inject constructor( Timber.v("backupKeys") // Sanity check, as this method can be called after a delay, the state may have change during the delay - if (!isEnabled || backupOlmPkEncryption == null || keysBackupVersion == null) { + if (!isEnabled() || backupOlmPkEncryption == null || keysBackupVersion == null) { Timber.v("backupKeys: Invalid configuration") backupAllGroupSessionsCallback?.onFailure(IllegalStateException("Invalid configuration")) resetBackupAllGroupSessionsListeners() return } - if (state === KeysBackupState.BackingUp) { + if (getState() === KeysBackupState.BackingUp) { // Do nothing if we are already backing up - Timber.v("backupKeys: Invalid state: $state") + Timber.v("backupKeys: Invalid state: ${getState()}") return } @@ -1360,7 +1378,7 @@ internal class DefaultKeysBackupService @Inject constructor( // Do not stay in KeysBackupState.WrongBackUpVersion but check what is available on the homeserver checkAndStartKeysBackup() } - else -> + else -> // Come back to the ready state so that we will retry on the next received key keysBackupStateManager.state = KeysBackupState.ReadyToBackUp } @@ -1482,8 +1500,10 @@ internal class DefaultKeysBackupService @Inject constructor( get() = cryptoStore @VisibleForTesting - fun createFakeKeysBackupVersion(keysBackupCreationInfo: MegolmBackupCreationInfo, - callback: MatrixCallback) { + fun createFakeKeysBackupVersion( + keysBackupCreationInfo: MegolmBackupCreationInfo, + callback: MatrixCallback + ) { @Suppress("UNCHECKED_CAST") val createKeysBackupVersionBody = CreateKeysBackupVersionBody( algorithm = keysBackupCreationInfo.algorithm, 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 f821fdcf6d..8560eb5fc1 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 @@ -49,8 +49,9 @@ internal data class GeneratePrivateKeyResult( * @return a {privateKey, salt, iterations} tuple. */ @WorkerThread -internal fun generatePrivateKeyWithPassword(password: String, - progressListener: ProgressListener? +internal fun generatePrivateKeyWithPassword( + password: String, + progressListener: ProgressListener? ): GeneratePrivateKeyResult { val salt = generateSalt() val iterations = DEFAULT_ITERATION @@ -70,10 +71,12 @@ internal fun generatePrivateKeyWithPassword(password: String, * @return a private key. */ @WorkerThread -internal fun retrievePrivateKeyWithPassword(password: String, - salt: String, - iterations: Int, - progressListener: ProgressListener? = null): ByteArray { +internal fun retrievePrivateKeyWithPassword( + password: String, + salt: String, + iterations: Int, + progressListener: ProgressListener? = null +): ByteArray { return deriveKey(password, salt, iterations, progressListener) } @@ -88,10 +91,12 @@ internal fun retrievePrivateKeyWithPassword(password: String, * @return a private key. */ @WorkerThread -internal fun deriveKey(password: String, - salt: String, - iterations: Int, - progressListener: ProgressListener?): ByteArray { +internal fun deriveKey( + password: String, + salt: String, + iterations: Int, + progressListener: ProgressListener? +): ByteArray { // Note: copied and adapted from MXMegolmExportEncryption // 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. diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/keysbackup/KeysBackupStateManager.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/keysbackup/KeysBackupStateManager.kt index 78ef958bbf..0614eceb16 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/keysbackup/KeysBackupStateManager.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/keysbackup/KeysBackupStateManager.kt @@ -49,7 +49,7 @@ internal class KeysBackupStateManager(private val uiHandler: Handler) { state == KeysBackupState.BackingUp // True if unknown or bad state - val isStucked: Boolean + val isStuck: Boolean get() = state == KeysBackupState.Unknown || state == KeysBackupState.Disabled || state == KeysBackupState.WrongBackUpVersion || diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/keysbackup/api/RoomKeysApi.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/keysbackup/api/RoomKeysApi.kt index d9c63b46ab..48a735018a 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/keysbackup/api/RoomKeysApi.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/keysbackup/api/RoomKeysApi.kt @@ -71,8 +71,10 @@ internal interface RoomKeysApi { * @param updateKeysBackupVersionBody the body */ @PUT(NetworkConstants.URI_API_PREFIX_PATH_UNSTABLE + "room_keys/version/{version}") - suspend fun updateKeysBackupVersion(@Path("version") version: String, - @Body updateKeysBackupVersionBody: UpdateKeysBackupVersionBody) + suspend fun updateKeysBackupVersion( + @Path("version") version: String, + @Body updateKeysBackupVersionBody: UpdateKeysBackupVersionBody + ) /* ========================================================================================== * Storing keys @@ -93,10 +95,12 @@ internal interface RoomKeysApi { * @param keyBackupData the data to send */ @PUT(NetworkConstants.URI_API_PREFIX_PATH_UNSTABLE + "room_keys/keys/{roomId}/{sessionId}") - suspend fun storeRoomSessionData(@Path("roomId") roomId: String, - @Path("sessionId") sessionId: String, - @Query("version") version: String, - @Body keyBackupData: KeyBackupData): BackupKeysResult + suspend fun storeRoomSessionData( + @Path("roomId") roomId: String, + @Path("sessionId") sessionId: String, + @Query("version") version: String, + @Body keyBackupData: KeyBackupData + ): BackupKeysResult /** * Store several keys for the given room, using the given backup version. @@ -106,9 +110,11 @@ internal interface RoomKeysApi { * @param roomKeysBackupData the data to send */ @PUT(NetworkConstants.URI_API_PREFIX_PATH_UNSTABLE + "room_keys/keys/{roomId}") - suspend fun storeRoomSessionsData(@Path("roomId") roomId: String, - @Query("version") version: String, - @Body roomKeysBackupData: RoomKeysBackupData): BackupKeysResult + suspend fun storeRoomSessionsData( + @Path("roomId") roomId: String, + @Query("version") version: String, + @Body roomKeysBackupData: RoomKeysBackupData + ): BackupKeysResult /** * Store several keys, using the given backup version. @@ -117,8 +123,10 @@ internal interface RoomKeysApi { * @param keysBackupData the data to send */ @PUT(NetworkConstants.URI_API_PREFIX_PATH_UNSTABLE + "room_keys/keys") - suspend fun storeSessionsData(@Query("version") version: String, - @Body keysBackupData: KeysBackupData): BackupKeysResult + suspend fun storeSessionsData( + @Query("version") version: String, + @Body keysBackupData: KeysBackupData + ): BackupKeysResult /* ========================================================================================== * Retrieving keys @@ -132,9 +140,11 @@ internal interface RoomKeysApi { * @param version the version of the backup, or empty String to retrieve the last version */ @GET(NetworkConstants.URI_API_PREFIX_PATH_UNSTABLE + "room_keys/keys/{roomId}/{sessionId}") - suspend fun getRoomSessionData(@Path("roomId") roomId: String, - @Path("sessionId") sessionId: String, - @Query("version") version: String): KeyBackupData + suspend fun getRoomSessionData( + @Path("roomId") roomId: String, + @Path("sessionId") sessionId: String, + @Query("version") version: String + ): KeyBackupData /** * Retrieve all the keys for the given room from the backup. @@ -143,8 +153,10 @@ internal interface RoomKeysApi { * @param version the version of the backup, or empty String to retrieve the last version */ @GET(NetworkConstants.URI_API_PREFIX_PATH_UNSTABLE + "room_keys/keys/{roomId}") - suspend fun getRoomSessionsData(@Path("roomId") roomId: String, - @Query("version") version: String): RoomKeysBackupData + suspend fun getRoomSessionsData( + @Path("roomId") roomId: String, + @Query("version") version: String + ): RoomKeysBackupData /** * Retrieve all the keys from the backup. @@ -162,16 +174,20 @@ internal interface RoomKeysApi { * Deletes keys from the backup. */ @DELETE(NetworkConstants.URI_API_PREFIX_PATH_UNSTABLE + "room_keys/keys/{roomId}/{sessionId}") - suspend fun deleteRoomSessionData(@Path("roomId") roomId: String, - @Path("sessionId") sessionId: String, - @Query("version") version: String) + suspend fun deleteRoomSessionData( + @Path("roomId") roomId: String, + @Path("sessionId") sessionId: String, + @Query("version") version: String + ) /** * Deletes keys from the backup. */ @DELETE(NetworkConstants.URI_API_PREFIX_PATH_UNSTABLE + "room_keys/keys/{roomId}") - suspend fun deleteRoomSessionsData(@Path("roomId") roomId: String, - @Query("version") version: String) + suspend fun deleteRoomSessionsData( + @Path("roomId") roomId: String, + @Query("version") version: String + ) /** * Deletes keys from the backup. diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/model/rest/KeyVerificationAccept.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/model/rest/KeyVerificationAccept.kt index 9fdeda01c9..85ba1762d3 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/model/rest/KeyVerificationAccept.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/model/rest/KeyVerificationAccept.kt @@ -69,12 +69,14 @@ internal data class KeyVerificationAccept( override fun toSendToDeviceObject() = this companion object : VerificationInfoAcceptFactory { - override fun create(tid: String, - keyAgreementProtocol: String, - hash: String, - commitment: String, - messageAuthenticationCode: String, - shortAuthenticationStrings: List): VerificationInfoAccept { + override fun create( + tid: String, + keyAgreementProtocol: String, + hash: String, + commitment: String, + messageAuthenticationCode: String, + shortAuthenticationStrings: List + ): VerificationInfoAccept { return KeyVerificationAccept( transactionId = tid, keyAgreementProtocol = keyAgreementProtocol, diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/model/rest/VerificationMethodValues.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/model/rest/VerificationMethodValues.kt index c07434f586..afe6191bf7 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/model/rest/VerificationMethodValues.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/model/rest/VerificationMethodValues.kt @@ -28,7 +28,7 @@ internal const val VERIFICATION_METHOD_RECIPROCATE = "m.reciprocate.v1" internal fun VerificationMethod.toValue(): String { return when (this) { - VerificationMethod.SAS -> VERIFICATION_METHOD_SAS + VerificationMethod.SAS -> VERIFICATION_METHOD_SAS VerificationMethod.QR_CODE_SCAN -> VERIFICATION_METHOD_QR_CODE_SCAN VerificationMethod.QR_CODE_SHOW -> VERIFICATION_METHOD_QR_CODE_SHOW } 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 1ded9c6c7e..ddb048a912 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 @@ -30,6 +30,7 @@ import org.matrix.android.sdk.api.session.securestorage.EncryptedSecretContent import org.matrix.android.sdk.api.session.securestorage.IntegrityResult import org.matrix.android.sdk.api.session.securestorage.KeyInfo import org.matrix.android.sdk.api.session.securestorage.KeyInfoResult +import org.matrix.android.sdk.api.session.securestorage.KeyRef import org.matrix.android.sdk.api.session.securestorage.KeySigner import org.matrix.android.sdk.api.session.securestorage.RawBytesKeySpec import org.matrix.android.sdk.api.session.securestorage.SecretStorageKeyContent @@ -62,10 +63,12 @@ internal class DefaultSharedSecretStorageService @Inject constructor( private val cryptoCoroutineScope: CoroutineScope ) : SharedSecretStorageService { - override suspend fun generateKey(keyId: String, - key: SsssKeySpec?, - keyName: String, - keySigner: KeySigner?): SsssKeyCreationInfo { + override suspend fun generateKey( + keyId: String, + key: SsssKeySpec?, + keyName: String, + keySigner: KeySigner? + ): SsssKeyCreationInfo { return withContext(cryptoCoroutineScope.coroutineContext + coroutineDispatchers.computation) { val bytes = (key as? RawBytesKeySpec)?.privateKey ?: ByteArray(32).also { @@ -94,11 +97,13 @@ internal class DefaultSharedSecretStorageService @Inject constructor( } } - override suspend fun generateKeyWithPassphrase(keyId: String, - keyName: String, - passphrase: String, - keySigner: KeySigner, - progressListener: ProgressListener?): SsssKeyCreationInfo { + override suspend fun generateKeyWithPassphrase( + keyId: String, + keyName: String, + passphrase: String, + keySigner: KeySigner, + progressListener: ProgressListener? + ): SsssKeyCreationInfo { return withContext(cryptoCoroutineScope.coroutineContext + coroutineDispatchers.computation) { val privatePart = generatePrivateKeyWithPassword(passphrase, progressListener) @@ -157,7 +162,7 @@ internal class DefaultSharedSecretStorageService @Inject constructor( return getKey(keyId) } - override suspend fun storeSecret(name: String, secretBase64: String, keys: List) { + override suspend fun storeSecret(name: String, secretBase64: String, keys: List) { withContext(cryptoCoroutineScope.coroutineContext + coroutineDispatchers.computation) { val encryptedContents = HashMap() keys.forEach { @@ -174,7 +179,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 } } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/IMXCryptoStore.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/IMXCryptoStore.kt index 9b1c785059..b5b8d8e974 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/IMXCryptoStore.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/IMXCryptoStore.kt @@ -192,10 +192,12 @@ internal interface IMXCryptoStore { */ fun storeUserDevices(userId: String, devices: Map?) - fun storeUserCrossSigningKeys(userId: String, - masterKey: CryptoCrossSigningKey?, - selfSigningKey: CryptoCrossSigningKey?, - userSigningKey: CryptoCrossSigningKey?) + fun storeUserCrossSigningKeys( + userId: String, + masterKey: CryptoCrossSigningKey?, + selfSigningKey: CryptoCrossSigningKey?, + userSigningKey: CryptoCrossSigningKey? + ) /** * Retrieve the known devices for a user. @@ -392,7 +394,8 @@ internal interface IMXCryptoStore { algorithm: String, senderKey: String, fromDevice: String?, - event: Event) + event: Event + ) fun deleteOutgoingRoomKeyRequest(requestId: String) fun deleteOutgoingRoomKeyRequestInState(state: OutgoingRoomKeyRequestState) @@ -479,8 +482,14 @@ internal interface IMXCryptoStore { fun addWithHeldMegolmSession(withHeldContent: RoomKeyWithHeldContent) fun getWithHeldMegolmSession(roomId: String, sessionId: String): RoomKeyWithHeldContent? - fun markedSessionAsShared(roomId: String?, sessionId: String, userId: String, deviceId: String, - deviceIdentityKey: String, chainIndex: Int) + fun markedSessionAsShared( + roomId: String?, + sessionId: String, + userId: String, + deviceId: String, + deviceIdentityKey: String, + chainIndex: Int + ) /** * Query for information on this session sharing history. @@ -504,6 +513,5 @@ internal interface IMXCryptoStore { fun setDeviceKeysUploaded(uploaded: Boolean) fun areDeviceKeysUploaded(): Boolean fun tidyUpDataBase() - fun logDbUsageInfo() fun getOutgoingRoomKeyRequests(inStates: Set): List } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/RealmCryptoStore.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/RealmCryptoStore.kt index 533ab70bba..028d8f73f9 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 @@ -88,7 +88,6 @@ import org.matrix.android.sdk.internal.crypto.store.db.query.get import org.matrix.android.sdk.internal.crypto.store.db.query.getById import org.matrix.android.sdk.internal.crypto.store.db.query.getOrCreate import org.matrix.android.sdk.internal.crypto.util.RequestIdHelper -import org.matrix.android.sdk.internal.database.tools.RealmDebugTools import org.matrix.android.sdk.internal.di.CryptoDatabase import org.matrix.android.sdk.internal.di.DeviceId import org.matrix.android.sdk.internal.di.MoshiProvider @@ -322,10 +321,12 @@ internal class RealmCryptoStore @Inject constructor( } } - override fun storeUserCrossSigningKeys(userId: String, - masterKey: CryptoCrossSigningKey?, - selfSigningKey: CryptoCrossSigningKey?, - userSigningKey: CryptoCrossSigningKey?) { + override fun storeUserCrossSigningKeys( + userId: String, + masterKey: CryptoCrossSigningKey?, + selfSigningKey: CryptoCrossSigningKey?, + userSigningKey: CryptoCrossSigningKey? + ) { doRealmTransaction(realmConfiguration) { realm -> UserEntity.getOrCreate(realm, userId) .let { userEntity -> @@ -1154,9 +1155,11 @@ internal class RealmCryptoStore @Inject constructor( } } - override fun getOrAddOutgoingRoomKeyRequest(requestBody: RoomKeyRequestBody, - recipients: Map>, - fromIndex: Int): OutgoingKeyRequest { + override fun getOrAddOutgoingRoomKeyRequest( + requestBody: RoomKeyRequestBody, + recipients: Map>, + fromIndex: Int + ): OutgoingKeyRequest { // Insert the request and return the one passed in parameter lateinit var request: OutgoingKeyRequest doRealmTransaction(realmConfiguration) { realm -> @@ -1220,12 +1223,14 @@ internal class RealmCryptoStore @Inject constructor( } } - override fun updateOutgoingRoomKeyReply(roomId: String, - sessionId: String, - algorithm: String, - senderKey: String, - fromDevice: String?, - event: Event) { + override fun updateOutgoingRoomKeyReply( + roomId: String, + sessionId: String, + algorithm: String, + senderKey: String, + fromDevice: String?, + event: Event + ) { doRealmTransaction(realmConfiguration) { realm -> realm.where() .equalTo(OutgoingKeyRequestEntityFields.ROOM_ID, roomId) @@ -1266,7 +1271,8 @@ internal class RealmCryptoStore @Inject constructor( senderKey: String, algorithm: String, fromUser: String, - fromDevice: String) { + fromDevice: String + ) { monarchy.writeAsync { realm -> val now = clock.epochMillis() realm.createObject().apply { @@ -1288,13 +1294,15 @@ internal class RealmCryptoStore @Inject constructor( } } - override fun saveWithheldAuditTrail(roomId: String, - sessionId: String, - senderKey: String, - algorithm: String, - code: WithHeldCode, - userId: String, - deviceId: String) { + override fun saveWithheldAuditTrail( + roomId: String, + sessionId: String, + senderKey: String, + algorithm: String, + code: WithHeldCode, + userId: String, + deviceId: String + ) { monarchy.writeAsync { realm -> val now = clock.epochMillis() realm.createObject().apply { @@ -1316,34 +1324,39 @@ internal class RealmCryptoStore @Inject constructor( } } - override fun saveForwardKeyAuditTrail(roomId: String, - sessionId: String, - senderKey: String, - algorithm: String, - userId: String, - deviceId: String, - chainIndex: Long?) { + override fun saveForwardKeyAuditTrail( + roomId: String, + sessionId: String, + senderKey: String, + algorithm: String, + userId: String, + deviceId: String, + chainIndex: Long? + ) { saveForwardKeyTrail(roomId, sessionId, senderKey, algorithm, userId, deviceId, chainIndex, false) } - override fun saveIncomingForwardKeyAuditTrail(roomId: String, - sessionId: String, - senderKey: String, - algorithm: String, - userId: String, - deviceId: String, - chainIndex: Long?) { + override fun saveIncomingForwardKeyAuditTrail( + roomId: String, + sessionId: String, + senderKey: String, + algorithm: String, + userId: String, + deviceId: String, + chainIndex: Long? + ) { saveForwardKeyTrail(roomId, sessionId, senderKey, algorithm, userId, deviceId, chainIndex, true) } - private fun saveForwardKeyTrail(roomId: String, - sessionId: String, - senderKey: String, - algorithm: String, - userId: String, - deviceId: String, - chainIndex: Long?, - incoming: Boolean + private fun saveForwardKeyTrail( + roomId: String, + sessionId: String, + senderKey: String, + algorithm: String, + userId: String, + deviceId: String, + chainIndex: Long?, + incoming: Boolean ) { monarchy.writeAsync { realm -> val now = clock.epochMillis() @@ -1617,12 +1630,14 @@ internal class RealmCryptoStore @Inject constructor( } } - override fun markedSessionAsShared(roomId: String?, - sessionId: String, - userId: String, - deviceId: String, - deviceIdentityKey: String, - chainIndex: Int) { + override fun markedSessionAsShared( + roomId: String?, + sessionId: String, + userId: String, + deviceId: String, + deviceIdentityKey: String, + chainIndex: Int + ) { doRealmTransaction(realmConfiguration) { realm -> SharedSessionEntity.create( realm = realm, @@ -1693,11 +1708,4 @@ internal class RealmCryptoStore @Inject constructor( // Can we do something for WithHeldSessionEntity? } } - - /** - * Prints out database info. - */ - override fun logDbUsageInfo() { - RealmDebugTools(realmConfiguration).logInfo("Crypto") - } } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/migration/MigrateCryptoTo004.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/migration/MigrateCryptoTo004.kt index e5bdd2aa9b..0a986892d9 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/migration/MigrateCryptoTo004.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/migration/MigrateCryptoTo004.kt @@ -104,10 +104,10 @@ internal class MigrateCryptoTo004(realm: DynamicRealm) : RealmMigrator(realm, 4) val trustLevel = realm.createObject("TrustLevelEntity") when (oldDevice.verified) { - MXDeviceInfo.DEVICE_VERIFICATION_UNKNOWN -> { + MXDeviceInfo.DEVICE_VERIFICATION_UNKNOWN -> { obj.setNull(DeviceInfoEntityFields.TRUST_LEVEL_ENTITY.`$`) } - MXDeviceInfo.DEVICE_VERIFICATION_BLOCKED -> { + MXDeviceInfo.DEVICE_VERIFICATION_BLOCKED -> { trustLevel.setNull(TrustLevelEntityFields.LOCALLY_VERIFIED) trustLevel.setNull(TrustLevelEntityFields.CROSS_SIGNED_VERIFIED) obj.setBoolean(DeviceInfoEntityFields.IS_BLOCKED, oldDevice.isBlocked) @@ -118,7 +118,7 @@ internal class MigrateCryptoTo004(realm: DynamicRealm) : RealmMigrator(realm, 4) trustLevel.setBoolean(TrustLevelEntityFields.CROSS_SIGNED_VERIFIED, false) obj.setObject(DeviceInfoEntityFields.TRUST_LEVEL_ENTITY.`$`, trustLevel) } - MXDeviceInfo.DEVICE_VERIFICATION_VERIFIED -> { + MXDeviceInfo.DEVICE_VERIFICATION_VERIFIED -> { trustLevel.setBoolean(TrustLevelEntityFields.LOCALLY_VERIFIED, true) trustLevel.setBoolean(TrustLevelEntityFields.CROSS_SIGNED_VERIFIED, false) obj.setObject(DeviceInfoEntityFields.TRUST_LEVEL_ENTITY.`$`, trustLevel) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/model/AuditTrailMapper.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/model/AuditTrailMapper.kt index 80ae4a8d0d..fe8e9f1db7 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/model/AuditTrailMapper.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/model/AuditTrailMapper.kt @@ -30,7 +30,7 @@ internal object AuditTrailMapper { fun map(entity: AuditTrailEntity): AuditTrail? { val contentJson = entity.contentJson ?: return null return when (entity.type) { - TrailType.OutgoingKeyForward.name -> { + TrailType.OutgoingKeyForward.name -> { val info = tryOrNull { MoshiProvider.providesMoshi().adapter(ForwardInfo::class.java).fromJson(contentJson) } ?: return null @@ -50,7 +50,7 @@ internal object AuditTrailMapper { info = info ) } - TrailType.IncomingKeyRequest.name -> { + TrailType.IncomingKeyRequest.name -> { val info = tryOrNull { MoshiProvider.providesMoshi().adapter(IncomingKeyRequestInfo::class.java).fromJson(contentJson) } ?: return null @@ -60,7 +60,7 @@ internal object AuditTrailMapper { info = info ) } - TrailType.IncomingKeyForward.name -> { + TrailType.IncomingKeyForward.name -> { val info = tryOrNull { MoshiProvider.providesMoshi().adapter(ForwardInfo::class.java).fromJson(contentJson) } ?: return null @@ -70,7 +70,7 @@ internal object AuditTrailMapper { info = info ) } - else -> { + else -> { AuditTrail( ageLocalTs = entity.ageLocalTs ?: 0, type = TrailType.Unknown, 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 83671b28d9..a4f6c279ac 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 @@ -33,7 +33,8 @@ internal open class OlmInboundGroupSessionEntity( // olmInboundGroupSessionData contains Json var olmInboundGroupSessionData: String? = null, // Indicate if the key has been backed up to the homeserver - var backedUp: Boolean = false) : + var backedUp: Boolean = false +) : RealmObject() { fun getInboundGroupSession(): OlmInboundGroupSessionWrapper2? { 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 1a637d76c4..9f010db288 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 @@ -25,11 +25,13 @@ import org.matrix.olm.OlmSession internal fun OlmSessionEntity.Companion.createPrimaryKey(sessionId: String, deviceKey: String) = "$sessionId|$deviceKey" // olmSessionData is a serialized OlmSession -internal open class OlmSessionEntity(@PrimaryKey var primaryKey: String = "", - var sessionId: String? = null, - var deviceKey: String? = null, - var olmSessionData: String? = null, - var lastReceivedMessageTs: Long = 0) : +internal open class OlmSessionEntity( + @PrimaryKey var primaryKey: String = "", + var sessionId: String? = null, + var deviceKey: String? = null, + var olmSessionData: String? = null, + var lastReceivedMessageTs: Long = 0 +) : RealmObject() { fun getOlmSession(): OlmSession? { diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/model/OutgoingKeyRequestEntity.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/model/OutgoingKeyRequestEntity.kt index 7a8ba18809..854d148b76 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/model/OutgoingKeyRequestEntity.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/model/OutgoingKeyRequestEntity.kt @@ -117,7 +117,7 @@ internal open class OutgoingKeyRequestEntity( private fun eventToResult(event: Event): RequestResult? { return when (event.getClearType()) { - EventType.ROOM_KEY_WITHHELD -> { + EventType.ROOM_KEY_WITHHELD -> { event.content.toModel()?.code?.let { RequestResult.Failure(it) } @@ -125,7 +125,7 @@ internal open class OutgoingKeyRequestEntity( EventType.FORWARDED_ROOM_KEY -> { RequestResult.Success((event.content?.get("chain_index") as? Number)?.toInt() ?: 0) } - else -> null + else -> null } } } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/query/SharedSessionQueries.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/query/SharedSessionQueries.kt index 8bf9794375..85dd50c88e 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/query/SharedSessionQueries.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/query/SharedSessionQueries.kt @@ -24,12 +24,14 @@ import org.matrix.android.sdk.api.crypto.MXCRYPTO_ALGORITHM_MEGOLM import org.matrix.android.sdk.internal.crypto.store.db.model.SharedSessionEntity import org.matrix.android.sdk.internal.crypto.store.db.model.SharedSessionEntityFields -internal fun SharedSessionEntity.Companion.get(realm: Realm, - roomId: String?, - sessionId: String, - userId: String, - deviceId: String, - deviceIdentityKey: String?): SharedSessionEntity? { +internal fun SharedSessionEntity.Companion.get( + realm: Realm, + roomId: String?, + sessionId: String, + userId: String, + deviceId: String, + deviceIdentityKey: String? +): SharedSessionEntity? { return realm.where() .equalTo(SharedSessionEntityFields.ROOM_ID, roomId) .equalTo(SharedSessionEntityFields.SESSION_ID, sessionId) @@ -48,12 +50,15 @@ internal fun SharedSessionEntity.Companion.get(realm: Realm, roomId: String?, se .findAll() } -internal fun SharedSessionEntity.Companion.create(realm: Realm, roomId: String?, - sessionId: String, - userId: String, - deviceId: String, - deviceIdentityKey: String, - chainIndex: Int): SharedSessionEntity { +internal fun SharedSessionEntity.Companion.create( + realm: Realm, + roomId: String?, + sessionId: String, + userId: String, + deviceId: String, + deviceIdentityKey: String, + chainIndex: Int +): SharedSessionEntity { return realm.createObject().apply { this.roomId = roomId this.algorithm = MXCRYPTO_ALGORITHM_MEGOLM diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/tasks/EncryptEventTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/tasks/EncryptEventTask.kt index 394c618968..a4b4cd0761 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/tasks/EncryptEventTask.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/tasks/EncryptEventTask.kt @@ -31,10 +31,11 @@ import org.matrix.android.sdk.internal.task.Task import javax.inject.Inject internal interface EncryptEventTask : Task { - data class Params(val roomId: String, - val event: Event, - /**Do not encrypt these keys, keep them as is in encrypted content (e.g. m.relates_to)*/ - val keepKeys: List? = null + data class Params( + val roomId: String, + val event: Event, + /**Do not encrypt these keys, keep them as is in encrypted content (e.g. m.relates_to)*/ + val keepKeys: List? = null ) } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/tasks/RedactEventTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/tasks/RedactEventTask.kt index d6a7f3c6a0..56bdc8cae8 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/tasks/RedactEventTask.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/tasks/RedactEventTask.kt @@ -32,7 +32,8 @@ internal interface RedactEventTask : Task { internal class DefaultRedactEventTask @Inject constructor( private val roomAPI: RoomAPI, - private val globalErrorReceiver: GlobalErrorReceiver) : RedactEventTask { + private val globalErrorReceiver: GlobalErrorReceiver +) : RedactEventTask { override suspend fun execute(params: RedactEventTask.Params): String { val response = executeRequest(globalErrorReceiver) { diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/tasks/SendEventTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/tasks/SendEventTask.kt index 50efe51bc1..fbd9d245d9 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/tasks/SendEventTask.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/tasks/SendEventTask.kt @@ -39,7 +39,8 @@ internal class DefaultSendEventTask @Inject constructor( private val encryptEventTask: EncryptEventTask, private val loadRoomMembersTask: LoadRoomMembersTask, private val roomAPI: RoomAPI, - private val globalErrorReceiver: GlobalErrorReceiver) : SendEventTask { + private val globalErrorReceiver: GlobalErrorReceiver +) : SendEventTask { override suspend fun execute(params: SendEventTask.Params): String { try { diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/tasks/SendVerificationMessageTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/tasks/SendVerificationMessageTask.kt index 7c52c62d92..944f41d488 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/tasks/SendVerificationMessageTask.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/tasks/SendVerificationMessageTask.kt @@ -37,7 +37,8 @@ internal class DefaultSendVerificationMessageTask @Inject constructor( private val encryptEventTask: EncryptEventTask, private val roomAPI: RoomAPI, private val cryptoSessionInfoProvider: CryptoSessionInfoProvider, - private val globalErrorReceiver: GlobalErrorReceiver) : SendVerificationMessageTask { + private val globalErrorReceiver: GlobalErrorReceiver +) : SendVerificationMessageTask { override suspend fun execute(params: SendVerificationMessageTask.Params): String { val event = handleEncryption(params) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/verification/DefaultIncomingSASDefaultVerificationTransaction.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/verification/DefaultIncomingSASDefaultVerificationTransaction.kt index a9d5cf1191..6b3bb1e641 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/verification/DefaultIncomingSASDefaultVerificationTransaction.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/verification/DefaultIncomingSASDefaultVerificationTransaction.kt @@ -60,26 +60,26 @@ internal class DefaultIncomingSASDefaultVerificationTransaction( override val uxState: IncomingSasVerificationTransaction.UxState get() { return when (val immutableState = state) { - is VerificationTxState.OnStarted -> IncomingSasVerificationTransaction.UxState.SHOW_ACCEPT + is VerificationTxState.OnStarted -> IncomingSasVerificationTransaction.UxState.SHOW_ACCEPT is VerificationTxState.SendingAccept, is VerificationTxState.Accepted, is VerificationTxState.OnKeyReceived, is VerificationTxState.SendingKey, - is VerificationTxState.KeySent -> IncomingSasVerificationTransaction.UxState.WAIT_FOR_KEY_AGREEMENT + is VerificationTxState.KeySent -> IncomingSasVerificationTransaction.UxState.WAIT_FOR_KEY_AGREEMENT is VerificationTxState.ShortCodeReady -> IncomingSasVerificationTransaction.UxState.SHOW_SAS is VerificationTxState.ShortCodeAccepted, is VerificationTxState.SendingMac, is VerificationTxState.MacSent, - is VerificationTxState.Verifying -> IncomingSasVerificationTransaction.UxState.WAIT_FOR_VERIFICATION - is VerificationTxState.Verified -> IncomingSasVerificationTransaction.UxState.VERIFIED - is VerificationTxState.Cancelled -> { + is VerificationTxState.Verifying -> IncomingSasVerificationTransaction.UxState.WAIT_FOR_VERIFICATION + is VerificationTxState.Verified -> IncomingSasVerificationTransaction.UxState.VERIFIED + is VerificationTxState.Cancelled -> { if (immutableState.byMe) { IncomingSasVerificationTransaction.UxState.CANCELLED_BY_ME } else { IncomingSasVerificationTransaction.UxState.CANCELLED_BY_OTHER } } - else -> IncomingSasVerificationTransaction.UxState.UNKNOWN + else -> IncomingSasVerificationTransaction.UxState.UNKNOWN } } @@ -232,7 +232,7 @@ internal class DefaultIncomingSASDefaultVerificationTransaction( val sasInfo = "MATRIX_KEY_VERIFICATION_SAS|$otherUserId|$otherDeviceId|$otherKey|$userId|$deviceId|${getSAS().publicKey}|$transactionId" return getSAS().generateShortCode(sasInfo, 6) } - else -> { + else -> { // Protocol has been checked earlier throw IllegalArgumentException() } 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 ddb8ed232b..f1cf1b7547 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 @@ -57,27 +57,27 @@ internal class DefaultOutgoingSASDefaultVerificationTransaction( override val uxState: OutgoingSasVerificationTransaction.UxState get() { return when (val immutableState = state) { - is VerificationTxState.None -> OutgoingSasVerificationTransaction.UxState.WAIT_FOR_START + is VerificationTxState.None -> OutgoingSasVerificationTransaction.UxState.WAIT_FOR_START is VerificationTxState.SendingStart, is VerificationTxState.Started, is VerificationTxState.OnAccepted, is VerificationTxState.SendingKey, is VerificationTxState.KeySent, - is VerificationTxState.OnKeyReceived -> OutgoingSasVerificationTransaction.UxState.WAIT_FOR_KEY_AGREEMENT + is VerificationTxState.OnKeyReceived -> OutgoingSasVerificationTransaction.UxState.WAIT_FOR_KEY_AGREEMENT is VerificationTxState.ShortCodeReady -> OutgoingSasVerificationTransaction.UxState.SHOW_SAS is VerificationTxState.ShortCodeAccepted, is VerificationTxState.SendingMac, is VerificationTxState.MacSent, - is VerificationTxState.Verifying -> OutgoingSasVerificationTransaction.UxState.WAIT_FOR_VERIFICATION - is VerificationTxState.Verified -> OutgoingSasVerificationTransaction.UxState.VERIFIED - is VerificationTxState.Cancelled -> { + is VerificationTxState.Verifying -> OutgoingSasVerificationTransaction.UxState.WAIT_FOR_VERIFICATION + is VerificationTxState.Verified -> OutgoingSasVerificationTransaction.UxState.VERIFIED + is VerificationTxState.Cancelled -> { if (immutableState.byMe) { OutgoingSasVerificationTransaction.UxState.CANCELLED_BY_OTHER } else { OutgoingSasVerificationTransaction.UxState.CANCELLED_BY_ME } } - else -> OutgoingSasVerificationTransaction.UxState.UNKNOWN + else -> OutgoingSasVerificationTransaction.UxState.UNKNOWN } } @@ -224,7 +224,7 @@ internal class DefaultOutgoingSASDefaultVerificationTransaction( val sasInfo = "MATRIX_KEY_VERIFICATION_SAS|$userId|$deviceId|${getSAS().publicKey}|$otherUserId|$otherDeviceId|$otherKey|$transactionId" return getSAS().generateShortCode(sasInfo, 6) } - else -> { + else -> { // Protocol has been checked earlier throw IllegalArgumentException() } 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 af48283767..1a04ee0302 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 @@ -129,31 +129,31 @@ internal class DefaultVerificationService @Inject constructor( Timber.d("## SAS onToDeviceEvent ${event.getClearType()}") cryptoCoroutineScope.launch(coroutineDispatchers.dmVerif) { when (event.getClearType()) { - EventType.KEY_VERIFICATION_START -> { + EventType.KEY_VERIFICATION_START -> { onStartRequestReceived(event) } - EventType.KEY_VERIFICATION_CANCEL -> { + EventType.KEY_VERIFICATION_CANCEL -> { onCancelReceived(event) } - EventType.KEY_VERIFICATION_ACCEPT -> { + EventType.KEY_VERIFICATION_ACCEPT -> { onAcceptReceived(event) } - EventType.KEY_VERIFICATION_KEY -> { + EventType.KEY_VERIFICATION_KEY -> { onKeyReceived(event) } - EventType.KEY_VERIFICATION_MAC -> { + EventType.KEY_VERIFICATION_MAC -> { onMacReceived(event) } - EventType.KEY_VERIFICATION_READY -> { + EventType.KEY_VERIFICATION_READY -> { onReadyReceived(event) } - EventType.KEY_VERIFICATION_DONE -> { + EventType.KEY_VERIFICATION_DONE -> { onDoneReceived(event) } MessageType.MSGTYPE_VERIFICATION_REQUEST -> { onRequestReceived(event) } - else -> { + else -> { // ignore } } @@ -163,7 +163,7 @@ internal class DefaultVerificationService @Inject constructor( fun onRoomEvent(event: Event) { cryptoCoroutineScope.launch(coroutineDispatchers.dmVerif) { when (event.getClearType()) { - EventType.KEY_VERIFICATION_START -> { + EventType.KEY_VERIFICATION_START -> { onRoomStartRequestReceived(event) } EventType.KEY_VERIFICATION_CANCEL -> { @@ -173,24 +173,24 @@ internal class DefaultVerificationService @Inject constructor( EventType.KEY_VERIFICATION_ACCEPT -> { onRoomAcceptReceived(event) } - EventType.KEY_VERIFICATION_KEY -> { + EventType.KEY_VERIFICATION_KEY -> { onRoomKeyRequestReceived(event) } - EventType.KEY_VERIFICATION_MAC -> { + EventType.KEY_VERIFICATION_MAC -> { onRoomMacReceived(event) } - EventType.KEY_VERIFICATION_READY -> { + EventType.KEY_VERIFICATION_READY -> { onRoomReadyReceived(event) } - EventType.KEY_VERIFICATION_DONE -> { + EventType.KEY_VERIFICATION_DONE -> { onRoomDoneReceived(event) } - EventType.MESSAGE -> { + EventType.MESSAGE -> { if (MessageType.MSGTYPE_VERIFICATION_REQUEST == event.getClearContent().toModel()?.msgType) { onRoomRequestReceived(event) } } - else -> { + else -> { // ignore } } @@ -472,9 +472,11 @@ internal class DefaultVerificationService @Inject constructor( /** * Return a CancelCode to make the caller cancel the verification. Else return null */ - private suspend fun handleStart(otherUserId: String?, - startReq: ValidVerificationInfoStart, - txConfigure: (DefaultVerificationTransaction) -> Unit): CancelCode? { + private suspend fun handleStart( + otherUserId: String?, + startReq: ValidVerificationInfoStart, + txConfigure: (DefaultVerificationTransaction) -> Unit + ): CancelCode? { Timber.d("## SAS onStartRequestReceived $startReq") if (otherUserId?.let { checkKeysAreDownloaded(it, startReq.fromDevice) } != null) { val tid = startReq.transactionId @@ -505,9 +507,9 @@ internal class DefaultVerificationService @Inject constructor( } when (startReq) { - is ValidVerificationInfoStart.SasVerificationInfoStart -> { + is ValidVerificationInfoStart.SasVerificationInfoStart -> { when (existing) { - is SasVerificationTransaction -> { + is SasVerificationTransaction -> { // should cancel both! Timber.v("## SAS onStartRequestReceived - Request exist with same id ${startReq.transactionId}") existing.cancel(CancelCode.UnexpectedMessage) @@ -517,7 +519,7 @@ internal class DefaultVerificationService @Inject constructor( is QrCodeVerificationTransaction -> { // Nothing to do? } - null -> { + null -> { getExistingTransactionsForUser(otherUserId) ?.filterIsInstance(SasVerificationTransaction::class.java) ?.takeIf { it.isNotEmpty() } @@ -589,8 +591,10 @@ internal class DefaultVerificationService @Inject constructor( } // TODO Refacto: It could just return a boolean - private suspend fun checkKeysAreDownloaded(otherUserId: String, - otherDeviceId: String): MXUsersDevicesMap? { + private suspend fun checkKeysAreDownloaded( + otherUserId: String, + otherDeviceId: String + ): MXUsersDevicesMap? { return try { var keys = deviceListManager.downloadKeys(listOf(otherUserId), false) if (keys.getUserDeviceIds(otherUserId)?.contains(otherDeviceId) == true) { @@ -899,9 +903,11 @@ internal class DefaultVerificationService @Inject constructor( } } - private fun handleReadyReceived(senderId: String, - readyReq: ValidVerificationInfoReady, - transportCreator: (DefaultVerificationTransaction) -> VerificationTransport) { + private fun handleReadyReceived( + senderId: String, + readyReq: ValidVerificationInfoReady, + transportCreator: (DefaultVerificationTransaction) -> VerificationTransport + ) { val existingRequest = getExistingVerificationRequests(senderId).find { it.transactionId == readyReq.transactionId } if (existingRequest == null) { Timber.e("## SAS Received Ready for unknown request txId:${readyReq.transactionId} fromDevice ${readyReq.fromDevice}") @@ -939,7 +945,7 @@ internal class DefaultVerificationService @Inject constructor( updatePendingRequest( existingRequest.copy( - readyInfo = readyReq + readyInfo = readyReq ) ) @@ -967,12 +973,12 @@ internal class DefaultVerificationService @Inject constructor( } return when { - userId != otherUserId -> + userId != otherUserId -> createQrCodeDataForDistinctUser(requestId, otherUserId) crossSigningService.isCrossSigningVerified() -> // This is a self verification and I am the old device (Osborne2) createQrCodeDataForVerifiedDevice(requestId, otherDeviceId) - else -> + else -> // This is a self verification and I am the new device (Dynabook) createQrCodeDataForUnVerifiedDevice(requestId) } @@ -1158,10 +1164,12 @@ internal class DefaultVerificationService @Inject constructor( } } - override fun requestKeyVerificationInDMs(methods: List, - otherUserId: String, - roomId: String, - localId: String?): PendingVerificationRequest { + override fun requestKeyVerificationInDMs( + methods: List, + otherUserId: String, + roomId: String, + localId: String? + ): PendingVerificationRequest { Timber.i("## SAS Requesting verification to user: $otherUserId in room $roomId") val requestsForUser = pendingRequests.getOrPut(otherUserId) { mutableListOf() } @@ -1328,11 +1336,13 @@ internal class DefaultVerificationService @Inject constructor( dispatchRequestUpdated(updated) } - override fun beginKeyVerificationInDMs(method: VerificationMethod, - transactionId: String, - roomId: String, - otherUserId: String, - otherDeviceId: String): String { + override fun beginKeyVerificationInDMs( + method: VerificationMethod, + transactionId: String, + roomId: String, + otherUserId: String, + otherDeviceId: String + ): String { if (method == VerificationMethod.SAS) { val tx = DefaultOutgoingSASDefaultVerificationTransaction( setDeviceVerificationAction, @@ -1357,10 +1367,12 @@ internal class DefaultVerificationService @Inject constructor( } } - override fun readyPendingVerificationInDMs(methods: List, - otherUserId: String, - roomId: String, - transactionId: String): Boolean { + override fun readyPendingVerificationInDMs( + methods: List, + otherUserId: String, + roomId: String, + transactionId: String + ): Boolean { Timber.v("## SAS readyPendingVerificationInDMs $otherUserId room:$roomId tx:$transactionId") // Let's find the related request val existingRequest = getExistingVerificationRequest(otherUserId, transactionId) @@ -1399,9 +1411,11 @@ internal class DefaultVerificationService @Inject constructor( } } - override fun readyPendingVerification(methods: List, - otherUserId: String, - transactionId: String): Boolean { + override fun readyPendingVerification( + methods: List, + otherUserId: String, + transactionId: String + ): Boolean { Timber.v("## SAS readyPendingVerification $otherUserId tx:$transactionId") // Let's find the related request val existingRequest = getExistingVerificationRequest(otherUserId, transactionId) @@ -1445,7 +1459,8 @@ internal class DefaultVerificationService @Inject constructor( otherDeviceId: String, otherUserMethods: List?, methods: List, - transportCreator: (DefaultVerificationTransaction) -> VerificationTransport): List { + transportCreator: (DefaultVerificationTransaction) -> VerificationTransport + ): List { if (otherUserMethods.isNullOrEmpty()) { return emptyList() } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/verification/DefaultVerificationTransaction.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/verification/DefaultVerificationTransaction.kt index 27bc764395..9d19fd137e 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/verification/DefaultVerificationTransaction.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/verification/DefaultVerificationTransaction.kt @@ -37,7 +37,8 @@ internal abstract class DefaultVerificationTransaction( override val transactionId: String, override val otherUserId: String, override var otherDeviceId: String? = null, - override val isIncoming: Boolean) : VerificationTransaction { + override val isIncoming: Boolean +) : VerificationTransaction { lateinit var transport: VerificationTransport @@ -55,9 +56,12 @@ internal abstract class DefaultVerificationTransaction( listeners.remove(listener) } - protected fun trust(canTrustOtherUserMasterKey: Boolean, - toVerifyDeviceIds: List, - eventuallyMarkMyMasterKeyAsTrusted: Boolean, autoDone: Boolean = true) { + protected fun trust( + canTrustOtherUserMasterKey: Boolean, + toVerifyDeviceIds: List, + eventuallyMarkMyMasterKeyAsTrusted: Boolean, + autoDone: Boolean = true + ) { Timber.d("## Verification: trust ($otherUserId,$otherDeviceId) , verifiedDevices:$toVerifyDeviceIds") Timber.d("## Verification: trust Mark myMSK trusted $eventuallyMarkMyMasterKeyAsTrusted") diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/verification/SASDefaultVerificationTransaction.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/verification/SASDefaultVerificationTransaction.kt index 68dd57478e..1cbaff059a 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/verification/SASDefaultVerificationTransaction.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/verification/SASDefaultVerificationTransaction.kt @@ -314,11 +314,13 @@ internal abstract class SASDefaultVerificationTransaction( transport.cancelTransaction(transactionId, otherUserId, otherDeviceId ?: "", code) } - protected fun sendToOther(type: String, - keyToDevice: VerificationInfo, - nextState: VerificationTxState, - onErrorReason: CancelCode, - onDone: (() -> Unit)?) { + protected fun sendToOther( + type: String, + keyToDevice: VerificationInfo, + nextState: VerificationTxState, + onErrorReason: CancelCode, + onDone: (() -> Unit)? + ) { transport.sendToOther(type, keyToDevice, nextState, onErrorReason, onDone) } @@ -331,11 +333,11 @@ internal abstract class SASDefaultVerificationTransaction( if (shortCodeBytes!!.size < 5) return null return getDecimalCodeRepresentation(shortCodeBytes!!) } - SasMode.EMOJI -> { + SasMode.EMOJI -> { if (shortCodeBytes!!.size < 6) return null return getEmojiCodeRepresentation(shortCodeBytes!!).joinToString(" ") { it.emoji } } - else -> return null + else -> return null } } @@ -360,8 +362,8 @@ internal abstract class SASDefaultVerificationTransaction( private fun macUsingAgreedMethod(message: String, info: String): String? { return when (accepted?.messageAuthenticationCode?.lowercase(Locale.ROOT)) { SAS_MAC_SHA256_LONGKDF -> getSAS().calculateMacLongKdf(message, info) - SAS_MAC_SHA256 -> getSAS().calculateMac(message, info) - else -> null + SAS_MAC_SHA256 -> getSAS().calculateMac(message, info) + else -> null } } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/verification/VerificationEmoji.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/verification/VerificationEmoji.kt index eb1819fe6a..cff3591771 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/verification/VerificationEmoji.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/verification/VerificationEmoji.kt @@ -20,69 +20,69 @@ import org.matrix.android.sdk.api.session.crypto.verification.EmojiRepresentatio internal fun getEmojiForCode(code: Int): EmojiRepresentation { return when (code % 64) { - 0 -> EmojiRepresentation("🐶", R.string.verification_emoji_dog, R.drawable.ic_verification_dog) - 1 -> EmojiRepresentation("🐱", R.string.verification_emoji_cat, R.drawable.ic_verification_cat) - 2 -> EmojiRepresentation("🦁", R.string.verification_emoji_lion, R.drawable.ic_verification_lion) - 3 -> EmojiRepresentation("🐎", R.string.verification_emoji_horse, R.drawable.ic_verification_horse) - 4 -> EmojiRepresentation("🦄", R.string.verification_emoji_unicorn, R.drawable.ic_verification_unicorn) - 5 -> EmojiRepresentation("🐷", R.string.verification_emoji_pig, R.drawable.ic_verification_pig) - 6 -> EmojiRepresentation("🐘", R.string.verification_emoji_elephant, R.drawable.ic_verification_elephant) - 7 -> EmojiRepresentation("🐰", R.string.verification_emoji_rabbit, R.drawable.ic_verification_rabbit) - 8 -> EmojiRepresentation("🐼", R.string.verification_emoji_panda, R.drawable.ic_verification_panda) - 9 -> EmojiRepresentation("🐓", R.string.verification_emoji_rooster, R.drawable.ic_verification_rooster) - 10 -> EmojiRepresentation("🐧", R.string.verification_emoji_penguin, R.drawable.ic_verification_penguin) - 11 -> EmojiRepresentation("🐢", R.string.verification_emoji_turtle, R.drawable.ic_verification_turtle) - 12 -> EmojiRepresentation("🐟", R.string.verification_emoji_fish, R.drawable.ic_verification_fish) - 13 -> EmojiRepresentation("🐙", R.string.verification_emoji_octopus, R.drawable.ic_verification_octopus) - 14 -> EmojiRepresentation("🦋", R.string.verification_emoji_butterfly, R.drawable.ic_verification_butterfly) - 15 -> EmojiRepresentation("🌷", R.string.verification_emoji_flower, R.drawable.ic_verification_flower) - 16 -> EmojiRepresentation("🌳", R.string.verification_emoji_tree, R.drawable.ic_verification_tree) - 17 -> EmojiRepresentation("🌵", R.string.verification_emoji_cactus, R.drawable.ic_verification_cactus) - 18 -> EmojiRepresentation("🍄", R.string.verification_emoji_mushroom, R.drawable.ic_verification_mushroom) - 19 -> EmojiRepresentation("🌏", R.string.verification_emoji_globe, R.drawable.ic_verification_globe) - 20 -> EmojiRepresentation("🌙", R.string.verification_emoji_moon, R.drawable.ic_verification_moon) - 21 -> EmojiRepresentation("☁️", R.string.verification_emoji_cloud, R.drawable.ic_verification_cloud) - 22 -> EmojiRepresentation("🔥", R.string.verification_emoji_fire, R.drawable.ic_verification_fire) - 23 -> EmojiRepresentation("🍌", R.string.verification_emoji_banana, R.drawable.ic_verification_banana) - 24 -> EmojiRepresentation("🍎", R.string.verification_emoji_apple, R.drawable.ic_verification_apple) - 25 -> EmojiRepresentation("🍓", R.string.verification_emoji_strawberry, R.drawable.ic_verification_strawberry) - 26 -> EmojiRepresentation("🌽", R.string.verification_emoji_corn, R.drawable.ic_verification_corn) - 27 -> EmojiRepresentation("🍕", R.string.verification_emoji_pizza, R.drawable.ic_verification_pizza) - 28 -> EmojiRepresentation("🎂", R.string.verification_emoji_cake, R.drawable.ic_verification_cake) - 29 -> EmojiRepresentation("❤️", R.string.verification_emoji_heart, R.drawable.ic_verification_heart) - 30 -> EmojiRepresentation("🙂", R.string.verification_emoji_smiley, R.drawable.ic_verification_smiley) - 31 -> EmojiRepresentation("🤖", R.string.verification_emoji_robot, R.drawable.ic_verification_robot) - 32 -> EmojiRepresentation("🎩", R.string.verification_emoji_hat, R.drawable.ic_verification_hat) - 33 -> EmojiRepresentation("👓", R.string.verification_emoji_glasses, R.drawable.ic_verification_glasses) - 34 -> EmojiRepresentation("🔧", R.string.verification_emoji_spanner, R.drawable.ic_verification_spanner) - 35 -> EmojiRepresentation("🎅", R.string.verification_emoji_santa, R.drawable.ic_verification_santa) - 36 -> EmojiRepresentation("👍", R.string.verification_emoji_thumbs_up, R.drawable.ic_verification_thumbs_up) - 37 -> EmojiRepresentation("☂️", R.string.verification_emoji_umbrella, R.drawable.ic_verification_umbrella) - 38 -> EmojiRepresentation("⌛", R.string.verification_emoji_hourglass, R.drawable.ic_verification_hourglass) - 39 -> EmojiRepresentation("⏰", R.string.verification_emoji_clock, R.drawable.ic_verification_clock) - 40 -> EmojiRepresentation("🎁", R.string.verification_emoji_gift, R.drawable.ic_verification_gift) - 41 -> EmojiRepresentation("💡", R.string.verification_emoji_light_bulb, R.drawable.ic_verification_light_bulb) - 42 -> EmojiRepresentation("📕", R.string.verification_emoji_book, R.drawable.ic_verification_book) - 43 -> EmojiRepresentation("✏️", R.string.verification_emoji_pencil, R.drawable.ic_verification_pencil) - 44 -> EmojiRepresentation("📎", R.string.verification_emoji_paperclip, R.drawable.ic_verification_paperclip) - 45 -> EmojiRepresentation("✂️", R.string.verification_emoji_scissors, R.drawable.ic_verification_scissors) - 46 -> EmojiRepresentation("🔒", R.string.verification_emoji_lock, R.drawable.ic_verification_lock) - 47 -> EmojiRepresentation("🔑", R.string.verification_emoji_key, R.drawable.ic_verification_key) - 48 -> EmojiRepresentation("🔨", R.string.verification_emoji_hammer, R.drawable.ic_verification_hammer) - 49 -> EmojiRepresentation("☎️", R.string.verification_emoji_telephone, R.drawable.ic_verification_phone) - 50 -> EmojiRepresentation("🏁", R.string.verification_emoji_flag, R.drawable.ic_verification_flag) - 51 -> EmojiRepresentation("🚂", R.string.verification_emoji_train, R.drawable.ic_verification_train) - 52 -> EmojiRepresentation("🚲", R.string.verification_emoji_bicycle, R.drawable.ic_verification_bicycle) - 53 -> EmojiRepresentation("✈️", R.string.verification_emoji_aeroplane, R.drawable.ic_verification_aeroplane) - 54 -> EmojiRepresentation("🚀", R.string.verification_emoji_rocket, R.drawable.ic_verification_rocket) - 55 -> EmojiRepresentation("🏆", R.string.verification_emoji_trophy, R.drawable.ic_verification_trophy) - 56 -> EmojiRepresentation("⚽", R.string.verification_emoji_ball, R.drawable.ic_verification_ball) - 57 -> EmojiRepresentation("🎸", R.string.verification_emoji_guitar, R.drawable.ic_verification_guitar) - 58 -> EmojiRepresentation("🎺", R.string.verification_emoji_trumpet, R.drawable.ic_verification_trumpet) - 59 -> EmojiRepresentation("🔔", R.string.verification_emoji_bell, R.drawable.ic_verification_bell) - 60 -> EmojiRepresentation("⚓", R.string.verification_emoji_anchor, R.drawable.ic_verification_anchor) - 61 -> EmojiRepresentation("🎧", R.string.verification_emoji_headphones, R.drawable.ic_verification_headphones) - 62 -> EmojiRepresentation("📁", R.string.verification_emoji_folder, R.drawable.ic_verification_folder) + 0 -> EmojiRepresentation("🐶", R.string.verification_emoji_dog, R.drawable.ic_verification_dog) + 1 -> EmojiRepresentation("🐱", R.string.verification_emoji_cat, R.drawable.ic_verification_cat) + 2 -> EmojiRepresentation("🦁", R.string.verification_emoji_lion, R.drawable.ic_verification_lion) + 3 -> EmojiRepresentation("🐎", R.string.verification_emoji_horse, R.drawable.ic_verification_horse) + 4 -> EmojiRepresentation("🦄", R.string.verification_emoji_unicorn, R.drawable.ic_verification_unicorn) + 5 -> EmojiRepresentation("🐷", R.string.verification_emoji_pig, R.drawable.ic_verification_pig) + 6 -> EmojiRepresentation("🐘", R.string.verification_emoji_elephant, R.drawable.ic_verification_elephant) + 7 -> EmojiRepresentation("🐰", R.string.verification_emoji_rabbit, R.drawable.ic_verification_rabbit) + 8 -> EmojiRepresentation("🐼", R.string.verification_emoji_panda, R.drawable.ic_verification_panda) + 9 -> EmojiRepresentation("🐓", R.string.verification_emoji_rooster, R.drawable.ic_verification_rooster) + 10 -> EmojiRepresentation("🐧", R.string.verification_emoji_penguin, R.drawable.ic_verification_penguin) + 11 -> EmojiRepresentation("🐢", R.string.verification_emoji_turtle, R.drawable.ic_verification_turtle) + 12 -> EmojiRepresentation("🐟", R.string.verification_emoji_fish, R.drawable.ic_verification_fish) + 13 -> EmojiRepresentation("🐙", R.string.verification_emoji_octopus, R.drawable.ic_verification_octopus) + 14 -> EmojiRepresentation("🦋", R.string.verification_emoji_butterfly, R.drawable.ic_verification_butterfly) + 15 -> EmojiRepresentation("🌷", R.string.verification_emoji_flower, R.drawable.ic_verification_flower) + 16 -> EmojiRepresentation("🌳", R.string.verification_emoji_tree, R.drawable.ic_verification_tree) + 17 -> EmojiRepresentation("🌵", R.string.verification_emoji_cactus, R.drawable.ic_verification_cactus) + 18 -> EmojiRepresentation("🍄", R.string.verification_emoji_mushroom, R.drawable.ic_verification_mushroom) + 19 -> EmojiRepresentation("🌏", R.string.verification_emoji_globe, R.drawable.ic_verification_globe) + 20 -> EmojiRepresentation("🌙", R.string.verification_emoji_moon, R.drawable.ic_verification_moon) + 21 -> EmojiRepresentation("☁️", R.string.verification_emoji_cloud, R.drawable.ic_verification_cloud) + 22 -> EmojiRepresentation("🔥", R.string.verification_emoji_fire, R.drawable.ic_verification_fire) + 23 -> EmojiRepresentation("🍌", R.string.verification_emoji_banana, R.drawable.ic_verification_banana) + 24 -> EmojiRepresentation("🍎", R.string.verification_emoji_apple, R.drawable.ic_verification_apple) + 25 -> EmojiRepresentation("🍓", R.string.verification_emoji_strawberry, R.drawable.ic_verification_strawberry) + 26 -> EmojiRepresentation("🌽", R.string.verification_emoji_corn, R.drawable.ic_verification_corn) + 27 -> EmojiRepresentation("🍕", R.string.verification_emoji_pizza, R.drawable.ic_verification_pizza) + 28 -> EmojiRepresentation("🎂", R.string.verification_emoji_cake, R.drawable.ic_verification_cake) + 29 -> EmojiRepresentation("❤️", R.string.verification_emoji_heart, R.drawable.ic_verification_heart) + 30 -> EmojiRepresentation("🙂", R.string.verification_emoji_smiley, R.drawable.ic_verification_smiley) + 31 -> EmojiRepresentation("🤖", R.string.verification_emoji_robot, R.drawable.ic_verification_robot) + 32 -> EmojiRepresentation("🎩", R.string.verification_emoji_hat, R.drawable.ic_verification_hat) + 33 -> EmojiRepresentation("👓", R.string.verification_emoji_glasses, R.drawable.ic_verification_glasses) + 34 -> EmojiRepresentation("🔧", R.string.verification_emoji_spanner, R.drawable.ic_verification_spanner) + 35 -> EmojiRepresentation("🎅", R.string.verification_emoji_santa, R.drawable.ic_verification_santa) + 36 -> EmojiRepresentation("👍", R.string.verification_emoji_thumbs_up, R.drawable.ic_verification_thumbs_up) + 37 -> EmojiRepresentation("☂️", R.string.verification_emoji_umbrella, R.drawable.ic_verification_umbrella) + 38 -> EmojiRepresentation("⌛", R.string.verification_emoji_hourglass, R.drawable.ic_verification_hourglass) + 39 -> EmojiRepresentation("⏰", R.string.verification_emoji_clock, R.drawable.ic_verification_clock) + 40 -> EmojiRepresentation("🎁", R.string.verification_emoji_gift, R.drawable.ic_verification_gift) + 41 -> EmojiRepresentation("💡", R.string.verification_emoji_light_bulb, R.drawable.ic_verification_light_bulb) + 42 -> EmojiRepresentation("📕", R.string.verification_emoji_book, R.drawable.ic_verification_book) + 43 -> EmojiRepresentation("✏️", R.string.verification_emoji_pencil, R.drawable.ic_verification_pencil) + 44 -> EmojiRepresentation("📎", R.string.verification_emoji_paperclip, R.drawable.ic_verification_paperclip) + 45 -> EmojiRepresentation("✂️", R.string.verification_emoji_scissors, R.drawable.ic_verification_scissors) + 46 -> EmojiRepresentation("🔒", R.string.verification_emoji_lock, R.drawable.ic_verification_lock) + 47 -> EmojiRepresentation("🔑", R.string.verification_emoji_key, R.drawable.ic_verification_key) + 48 -> EmojiRepresentation("🔨", R.string.verification_emoji_hammer, R.drawable.ic_verification_hammer) + 49 -> EmojiRepresentation("☎️", R.string.verification_emoji_telephone, R.drawable.ic_verification_phone) + 50 -> EmojiRepresentation("🏁", R.string.verification_emoji_flag, R.drawable.ic_verification_flag) + 51 -> EmojiRepresentation("🚂", R.string.verification_emoji_train, R.drawable.ic_verification_train) + 52 -> EmojiRepresentation("🚲", R.string.verification_emoji_bicycle, R.drawable.ic_verification_bicycle) + 53 -> EmojiRepresentation("✈️", R.string.verification_emoji_aeroplane, R.drawable.ic_verification_aeroplane) + 54 -> EmojiRepresentation("🚀", R.string.verification_emoji_rocket, R.drawable.ic_verification_rocket) + 55 -> EmojiRepresentation("🏆", R.string.verification_emoji_trophy, R.drawable.ic_verification_trophy) + 56 -> EmojiRepresentation("⚽", R.string.verification_emoji_ball, R.drawable.ic_verification_ball) + 57 -> EmojiRepresentation("🎸", R.string.verification_emoji_guitar, R.drawable.ic_verification_guitar) + 58 -> EmojiRepresentation("🎺", R.string.verification_emoji_trumpet, R.drawable.ic_verification_trumpet) + 59 -> EmojiRepresentation("🔔", R.string.verification_emoji_bell, R.drawable.ic_verification_bell) + 60 -> EmojiRepresentation("⚓", R.string.verification_emoji_anchor, R.drawable.ic_verification_anchor) + 61 -> EmojiRepresentation("🎧", R.string.verification_emoji_headphones, R.drawable.ic_verification_headphones) + 62 -> EmojiRepresentation("📁", R.string.verification_emoji_folder, R.drawable.ic_verification_folder) /* 63 */ else -> EmojiRepresentation("📌", R.string.verification_emoji_pin, R.drawable.ic_verification_pin) } } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/verification/VerificationInfoAccept.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/verification/VerificationInfoAccept.kt index 3ddb0ca758..0b9287cb05 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/verification/VerificationInfoAccept.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/verification/VerificationInfoAccept.kt @@ -63,12 +63,14 @@ internal interface VerificationInfoAccept : VerificationInfo): VerificationInfoAccept + fun create( + tid: String, + keyAgreementProtocol: String, + hash: String, + commitment: String, + messageAuthenticationCode: String, + shortAuthenticationStrings: List + ): VerificationInfoAccept } internal data class ValidVerificationInfoAccept( diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/verification/VerificationInfoStart.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/verification/VerificationInfoStart.kt index d71bd7359e..66591fe00f 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/verification/VerificationInfoStart.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/verification/VerificationInfoStart.kt @@ -68,7 +68,7 @@ internal interface VerificationInfoStart : VerificationInfo { + VERIFICATION_METHOD_SAS -> { val validKeyAgreementProtocols = keyAgreementProtocols?.takeIf { it.isNotEmpty() } ?: return null val validHashes = hashes?.takeIf { it.contains("sha256") } ?: return null val validMessageAuthenticationCodes = messageAuthenticationCodes @@ -98,14 +98,15 @@ internal interface VerificationInfoStart : VerificationInfo null + else -> null } } } internal sealed class ValidVerificationInfoStart( open val transactionId: String, - open val fromDevice: String) { + open val fromDevice: String +) { data class SasVerificationInfoStart( override val transactionId: String, override val fromDevice: String, diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/verification/VerificationMessageProcessor.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/verification/VerificationMessageProcessor.kt index 303b6c5839..821663bcff 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 @@ -62,7 +62,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 (event.ageLocalTs != null && !VerificationService.isValidRequest(event.ageLocalTs, clock.epochMillis())) return Unit.also { + if (!VerificationService.isValidRequest(event.ageLocalTs, clock.epochMillis())) return Unit.also { Timber.d("## SAS Verification live observer: msgId: ${event.eventId} is outdated age:$event.ageLocalTs ms") } @@ -131,7 +131,7 @@ internal class VerificationMessageProcessor @Inject constructor( EventType.KEY_VERIFICATION_DONE -> { verificationService.onRoomEvent(event) } - EventType.MESSAGE -> { + EventType.MESSAGE -> { if (MessageType.MSGTYPE_VERIFICATION_REQUEST == event.getClearContent().toModel()?.msgType) { verificationService.onRoomRequestReceived(event) } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/verification/VerificationTransport.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/verification/VerificationTransport.kt index 69dec12ef3..5314c23870 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/verification/VerificationTransport.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/verification/VerificationTransport.kt @@ -28,11 +28,13 @@ internal interface VerificationTransport { /** * Sends a message. */ - fun sendToOther(type: String, - verificationInfo: VerificationInfo, - nextState: VerificationTxState, - onErrorReason: CancelCode, - onDone: (() -> Unit)?) + fun sendToOther( + type: String, + verificationInfo: VerificationInfo, + nextState: VerificationTxState, + onErrorReason: CancelCode, + onDone: (() -> Unit)? + ) /** * @param supportedMethods list of supported method by this client @@ -42,65 +44,85 @@ internal interface VerificationTransport { * @param toDevices list of device Ids * @param callback will be called with eventId and ValidVerificationInfoRequest in case of success */ - fun sendVerificationRequest(supportedMethods: List, - localId: String, - otherUserId: String, - roomId: String?, - toDevices: List?, - callback: (String?, ValidVerificationInfoRequest?) -> Unit) + fun sendVerificationRequest( + supportedMethods: List, + localId: String, + otherUserId: String, + roomId: String?, + toDevices: List?, + callback: (String?, ValidVerificationInfoRequest?) -> Unit + ) - fun cancelTransaction(transactionId: String, - otherUserId: String, - otherUserDeviceId: String?, - code: CancelCode) + fun cancelTransaction( + transactionId: String, + otherUserId: String, + otherUserDeviceId: String?, + code: CancelCode + ) - fun cancelTransaction(transactionId: String, - otherUserId: String, - otherUserDeviceIds: List, - code: CancelCode) + fun cancelTransaction( + transactionId: String, + otherUserId: String, + otherUserDeviceIds: List, + code: CancelCode + ) - fun done(transactionId: String, - onDone: (() -> Unit)?) + fun done( + transactionId: String, + onDone: (() -> Unit)? + ) /** * Creates an accept message suitable for this transport. */ - fun createAccept(tid: String, - keyAgreementProtocol: String, - hash: String, - commitment: String, - messageAuthenticationCode: String, - shortAuthenticationStrings: List): VerificationInfoAccept + fun createAccept( + tid: String, + keyAgreementProtocol: String, + hash: String, + commitment: String, + messageAuthenticationCode: String, + shortAuthenticationStrings: List + ): VerificationInfoAccept - fun createKey(tid: String, - pubKey: String): VerificationInfoKey + fun createKey( + tid: String, + pubKey: String + ): VerificationInfoKey /** * Create start for SAS verification. */ - fun createStartForSas(fromDevice: String, - transactionId: String, - keyAgreementProtocols: List, - hashes: List, - messageAuthenticationCodes: List, - shortAuthenticationStrings: List): VerificationInfoStart + fun createStartForSas( + fromDevice: String, + transactionId: String, + keyAgreementProtocols: List, + hashes: List, + messageAuthenticationCodes: List, + shortAuthenticationStrings: List + ): VerificationInfoStart /** * Create start for QR code verification. */ - fun createStartForQrCode(fromDevice: String, - transactionId: String, - sharedSecret: String): VerificationInfoStart + fun createStartForQrCode( + fromDevice: String, + transactionId: String, + sharedSecret: String + ): VerificationInfoStart fun createMac(tid: String, mac: Map, keys: String): VerificationInfoMac - fun createReady(tid: String, - fromDevice: String, - methods: List): VerificationInfoReady + fun createReady( + tid: String, + fromDevice: String, + methods: List + ): VerificationInfoReady // TODO Refactor - fun sendVerificationReady(keyReq: VerificationInfoReady, - otherUserId: String, - otherDeviceId: String?, - callback: (() -> Unit)?) + fun sendVerificationReady( + keyReq: VerificationInfoReady, + otherUserId: String, + otherDeviceId: String?, + callback: (() -> Unit)? + ) } 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 03df849d22..f38a604890 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 @@ -61,11 +61,13 @@ internal class VerificationTransportRoomMessage( private val verificationSenderScope = CoroutineScope(cryptoCoroutineScope.coroutineContext + dispatcher) private val sequencer = SemaphoreCoroutineSequencer() - override fun sendToOther(type: String, - verificationInfo: VerificationInfo, - nextState: VerificationTxState, - onErrorReason: CancelCode, - onDone: (() -> Unit)?) { + override fun sendToOther( + type: String, + verificationInfo: VerificationInfo, + nextState: VerificationTxState, + onErrorReason: CancelCode, + onDone: (() -> Unit)? + ) { Timber.d("## SAS sending msg type $type") Timber.v("## SAS sending msg info $verificationInfo") val event = createEventAndLocalEcho( @@ -92,12 +94,14 @@ internal class VerificationTransportRoomMessage( } } - override fun sendVerificationRequest(supportedMethods: List, - localId: String, - otherUserId: String, - roomId: String?, - toDevices: List?, - callback: (String?, ValidVerificationInfoRequest?) -> Unit) { + override fun sendVerificationRequest( + supportedMethods: List, + localId: String, + otherUserId: String, + roomId: String?, + toDevices: List?, + callback: (String?, ValidVerificationInfoRequest?) -> Unit + ) { Timber.d("## SAS sending verification request with supported methods: $supportedMethods") // This transport requires a room requireNotNull(roomId) @@ -160,11 +164,17 @@ internal class VerificationTransportRoomMessage( } } - override fun cancelTransaction(transactionId: String, otherUserId: String, otherUserDeviceIds: List, code: CancelCode) = - cancelTransaction(transactionId, otherUserId, null, code) + override fun cancelTransaction( + transactionId: String, + otherUserId: String, + otherUserDeviceIds: List, + code: CancelCode + ) = cancelTransaction(transactionId, otherUserId, null, code) - override fun done(transactionId: String, - onDone: (() -> Unit)?) { + override fun done( + transactionId: String, + onDone: (() -> Unit)? + ) { Timber.d("## SAS sending done for $transactionId") val event = createEventAndLocalEcho( type = EventType.KEY_VERIFICATION_DONE, @@ -191,12 +201,14 @@ internal class VerificationTransportRoomMessage( } } - override fun createAccept(tid: String, - keyAgreementProtocol: String, - hash: String, - commitment: String, - messageAuthenticationCode: String, - shortAuthenticationStrings: List): VerificationInfoAccept = + override fun createAccept( + tid: String, + keyAgreementProtocol: String, + hash: String, + commitment: String, + messageAuthenticationCode: String, + shortAuthenticationStrings: List + ): VerificationInfoAccept = MessageVerificationAcceptContent.create( tid, keyAgreementProtocol, @@ -210,12 +222,14 @@ internal class VerificationTransportRoomMessage( override fun createMac(tid: String, mac: Map, keys: String) = MessageVerificationMacContent.create(tid, mac, keys) - override fun createStartForSas(fromDevice: String, - transactionId: String, - keyAgreementProtocols: List, - hashes: List, - messageAuthenticationCodes: List, - shortAuthenticationStrings: List): VerificationInfoStart { + override fun createStartForSas( + fromDevice: String, + transactionId: String, + keyAgreementProtocols: List, + hashes: List, + messageAuthenticationCodes: List, + shortAuthenticationStrings: List + ): VerificationInfoStart { return MessageVerificationStartContent( fromDevice, hashes, @@ -231,9 +245,11 @@ internal class VerificationTransportRoomMessage( ) } - override fun createStartForQrCode(fromDevice: String, - transactionId: String, - sharedSecret: String): VerificationInfoStart { + override fun createStartForQrCode( + fromDevice: String, + transactionId: String, + sharedSecret: String + ): VerificationInfoStart { return MessageVerificationStartContent( fromDevice, null, @@ -274,10 +290,12 @@ internal class VerificationTransportRoomMessage( } } - override fun sendVerificationReady(keyReq: VerificationInfoReady, - otherUserId: String, - otherDeviceId: String?, - callback: (() -> Unit)?) { + override fun sendVerificationReady( + keyReq: VerificationInfoReady, + otherUserId: String, + otherDeviceId: String?, + callback: (() -> Unit)? + ) { // Not applicable (send event is called directly) Timber.w("## SAS ignored verification ready with methods: ${keyReq.methods}") } 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 974adf3888..861a7a3a77 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 @@ -47,12 +47,14 @@ internal class VerificationTransportToDevice( private val clock: Clock, ) : VerificationTransport { - override fun sendVerificationRequest(supportedMethods: List, - localId: String, - otherUserId: String, - roomId: String?, - toDevices: List?, - callback: (String?, ValidVerificationInfoRequest?) -> Unit) { + override fun sendVerificationRequest( + supportedMethods: List, + localId: String, + otherUserId: String, + roomId: String?, + toDevices: List?, + callback: (String?, ValidVerificationInfoRequest?) -> Unit + ) { Timber.d("## SAS sending verification request with supported methods: $supportedMethods") val contentMap = MXUsersDevicesMap() val validKeyReq = ValidVerificationInfoRequest( @@ -86,10 +88,12 @@ internal class VerificationTransportToDevice( .executeBy(taskExecutor) } - override fun sendVerificationReady(keyReq: VerificationInfoReady, - otherUserId: String, - otherDeviceId: String?, - callback: (() -> Unit)?) { + override fun sendVerificationReady( + keyReq: VerificationInfoReady, + otherUserId: String, + otherDeviceId: String?, + callback: (() -> Unit)? + ) { Timber.d("## SAS sending verification ready with methods: ${keyReq.methods}") val contentMap = MXUsersDevicesMap() @@ -111,11 +115,13 @@ internal class VerificationTransportToDevice( .executeBy(taskExecutor) } - override fun sendToOther(type: String, - verificationInfo: VerificationInfo, - nextState: VerificationTxState, - onErrorReason: CancelCode, - onDone: (() -> Unit)?) { + override fun sendToOther( + type: String, + verificationInfo: VerificationInfo, + nextState: VerificationTxState, + onErrorReason: CancelCode, + onDone: (() -> Unit)? + ) { Timber.d("## SAS sending msg type $type") Timber.v("## SAS sending msg info $verificationInfo") val stateBeforeCall = tx?.state @@ -214,12 +220,14 @@ internal class VerificationTransportToDevice( .executeBy(taskExecutor) } - override fun createAccept(tid: String, - keyAgreementProtocol: String, - hash: String, - commitment: String, - messageAuthenticationCode: String, - shortAuthenticationStrings: List): VerificationInfoAccept = KeyVerificationAccept.create( + override fun createAccept( + tid: String, + keyAgreementProtocol: String, + hash: String, + commitment: String, + messageAuthenticationCode: String, + shortAuthenticationStrings: List + ): VerificationInfoAccept = KeyVerificationAccept.create( tid, keyAgreementProtocol, hash, @@ -232,12 +240,14 @@ internal class VerificationTransportToDevice( override fun createMac(tid: String, mac: Map, keys: String) = KeyVerificationMac.create(tid, mac, keys) - override fun createStartForSas(fromDevice: String, - transactionId: String, - keyAgreementProtocols: List, - hashes: List, - messageAuthenticationCodes: List, - shortAuthenticationStrings: List): VerificationInfoStart { + override fun createStartForSas( + fromDevice: String, + transactionId: String, + keyAgreementProtocols: List, + hashes: List, + messageAuthenticationCodes: List, + shortAuthenticationStrings: List + ): VerificationInfoStart { return KeyVerificationStart( fromDevice, VERIFICATION_METHOD_SAS, @@ -250,9 +260,11 @@ internal class VerificationTransportToDevice( ) } - override fun createStartForQrCode(fromDevice: String, - transactionId: String, - sharedSecret: String): VerificationInfoStart { + override fun createStartForQrCode( + fromDevice: String, + transactionId: String, + sharedSecret: String + ): VerificationInfoStart { return KeyVerificationStart( fromDevice, VERIFICATION_METHOD_RECIPROCATE, diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/verification/qrcode/DefaultQrCodeVerificationTransaction.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/verification/qrcode/DefaultQrCodeVerificationTransaction.kt index b1ad4d87b6..690ac12268 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/verification/qrcode/DefaultQrCodeVerificationTransaction.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/verification/qrcode/DefaultQrCodeVerificationTransaction.kt @@ -94,7 +94,7 @@ internal class DefaultQrCodeVerificationTransaction( // Check the other device view of my MSK when (otherQrCodeData) { - is QrCodeData.VerifyingAnotherUser -> { + is QrCodeData.VerifyingAnotherUser -> { // key2 (aka otherUserMasterCrossSigningPublicKey) is what the one displaying the QR code (other user) think my MSK is. // Let's check that it's correct // If not -> Cancel @@ -104,7 +104,7 @@ internal class DefaultQrCodeVerificationTransaction( return } else Unit } - is QrCodeData.SelfVerifyingMasterKeyTrusted -> { + is QrCodeData.SelfVerifyingMasterKeyTrusted -> { // key1 (aka userMasterCrossSigningPublicKey) is the session displaying the QR code view of our MSK. // Let's check that I see the same MSK // If not -> Cancel @@ -135,7 +135,7 @@ internal class DefaultQrCodeVerificationTransaction( // Let's now check the other user/device key material when (otherQrCodeData) { - is QrCodeData.VerifyingAnotherUser -> { + is QrCodeData.VerifyingAnotherUser -> { // key1(aka userMasterCrossSigningPublicKey) is the MSK of the one displaying the QR code (i.e other user) // Let's check that it matches what I think it should be if (otherQrCodeData.userMasterCrossSigningPublicKey @@ -149,7 +149,7 @@ internal class DefaultQrCodeVerificationTransaction( Unit } } - is QrCodeData.SelfVerifyingMasterKeyTrusted -> { + is QrCodeData.SelfVerifyingMasterKeyTrusted -> { // key2 (aka otherDeviceKey) is my current device key in POV of the one displaying the QR code (i.e other device) // Let's check that it's correct if (otherQrCodeData.otherDeviceKey @@ -259,11 +259,11 @@ internal class DefaultQrCodeVerificationTransaction( override fun otherUserScannedMyQrCode() { when (qrCodeData) { - is QrCodeData.VerifyingAnotherUser -> { + is QrCodeData.VerifyingAnotherUser -> { // Alice telling Bob that the code was scanned successfully is sufficient for Bob to trust Alice's key, trust(true, emptyList(), false) } - is QrCodeData.SelfVerifyingMasterKeyTrusted -> { + is QrCodeData.SelfVerifyingMasterKeyTrusted -> { // I now know that I have the correct device key for other session, // and can sign it with the self-signing key and upload the signature trust(false, listOf(otherDeviceId ?: ""), false) @@ -272,7 +272,7 @@ internal class DefaultQrCodeVerificationTransaction( // I now know that i can trust my MSK trust(true, emptyList(), true) } - null -> Unit + null -> Unit } } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/verification/qrcode/Extensions.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/verification/qrcode/Extensions.kt index b80c29c244..a0202485d6 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/verification/qrcode/Extensions.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/verification/qrcode/Extensions.kt @@ -36,8 +36,8 @@ internal fun QrCodeData.toEncodedString(): String { // Mode result += when (this) { - is QrCodeData.VerifyingAnotherUser -> 0 - is QrCodeData.SelfVerifyingMasterKeyTrusted -> 1 + is QrCodeData.VerifyingAnotherUser -> 0 + is QrCodeData.SelfVerifyingMasterKeyTrusted -> 1 is QrCodeData.SelfVerifyingMasterKeyNotTrusted -> 2 }.toByte() @@ -119,9 +119,9 @@ internal fun String.toQrCodeData(): QrCodeData? { val secret = byteArray.copyOfRange(cursor, byteArray.size).toBase64NoPadding() return when (mode) { - 0 -> QrCodeData.VerifyingAnotherUser(transactionId, key1, key2, secret) - 1 -> QrCodeData.SelfVerifyingMasterKeyTrusted(transactionId, key1, key2, secret) - 2 -> QrCodeData.SelfVerifyingMasterKeyNotTrusted(transactionId, key1, key2, secret) + 0 -> QrCodeData.VerifyingAnotherUser(transactionId, key1, key2, secret) + 1 -> QrCodeData.SelfVerifyingMasterKeyTrusted(transactionId, key1, key2, secret) + 2 -> QrCodeData.SelfVerifyingMasterKeyNotTrusted(transactionId, key1, key2, secret) else -> null } } 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 751992fa7f..d1ca4f48a6 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,10 +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 kotlinx.coroutines.sync.Mutex +import kotlinx.coroutines.sync.withLock import org.matrix.android.sdk.internal.database.mapper.asDomain import org.matrix.android.sdk.internal.database.model.EventEntity import org.matrix.android.sdk.internal.database.model.EventInsertEntity @@ -33,69 +32,64 @@ import org.matrix.android.sdk.internal.session.EventInsertLiveProcessor import timber.log.Timber import javax.inject.Inject -internal class EventInsertLiveObserver @Inject constructor(@SessionDatabase realmConfiguration: RealmConfiguration, - private val processors: Set<@JvmSuppressWildcards EventInsertLiveProcessor>) : +internal class EventInsertLiveObserver @Inject constructor( + @SessionDatabase realmConfiguration: RealmConfiguration, + private val processors: Set<@JvmSuppressWildcards EventInsertLiveProcessor> +) : RealmLiveEntityObserver(realmConfiguration) { + private val lock = Mutex() + 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") - results.forEach { - if (shouldProcess(it)) { - // don't use copy from realm over there - val copiedEvent = EventInsertEntity( - eventId = it.eventId, - eventType = it.eventType - ).apply { - insertType = it.insertType + observerScope.launch { + lock.withLock { + if (!results.isLoaded || results.isEmpty()) { + return@withLock } - filteredEvents.add(copiedEvent) + val idsToDeleteAfterProcess = ArrayList() + val filteredEvents = ArrayList(results.size) + Timber.v("EventInsertEntity updated with ${results.size} results in db") + results.forEach { + if (shouldProcess(it)) { + // don't use copy from realm over there + val copiedEvent = EventInsertEntity( + eventId = it.eventId, + eventType = it.eventType + ).apply { + insertType = it.insertType + } + filteredEvents.add(copiedEvent) + } + idsToDeleteAfterProcess.add(it.eventId) + } + 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() } } - idsToDeleteAfterProcess.add(it.eventId) } - - 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() } } private fun shouldProcess(eventInsertEntity: EventInsertEntity): Boolean { diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/RealmKeysUtils.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/RealmKeysUtils.kt index 392c1a7185..86355ceaa8 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/RealmKeysUtils.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/RealmKeysUtils.kt @@ -21,7 +21,7 @@ import androidx.core.content.edit import io.realm.Realm import io.realm.RealmConfiguration import org.matrix.android.sdk.BuildConfig -import org.matrix.android.sdk.internal.session.securestorage.SecretStoringUtils +import org.matrix.android.sdk.api.securestorage.SecretStoringUtils import timber.log.Timber import java.security.SecureRandom import javax.inject.Inject @@ -38,8 +38,10 @@ import javax.inject.Inject * then we generate a random secret key. The database key is encrypted with the secret key; The secret * key is encrypted with the public RSA key and stored with the encrypted key in the shared pref */ -internal class RealmKeysUtils @Inject constructor(context: Context, - private val secretStoringUtils: SecretStoringUtils) { +internal class RealmKeysUtils @Inject constructor( + context: Context, + private val secretStoringUtils: SecretStoringUtils, +) { private val rng = SecureRandom() @@ -69,7 +71,7 @@ internal class RealmKeysUtils @Inject constructor(context: Context, private fun createAndSaveKeyForDatabase(alias: String): ByteArray { val key = generateKeyForRealm() val encodedKey = Base64.encodeToString(key, Base64.NO_PADDING) - val toStore = secretStoringUtils.securelyStoreString(encodedKey, alias) + val toStore = secretStoringUtils.securelyStoreBytes(encodedKey.toByteArray(), alias) sharedPreferences.edit { putString("${ENCRYPTED_KEY_PREFIX}_$alias", Base64.encodeToString(toStore, Base64.NO_PADDING)) } @@ -83,7 +85,7 @@ internal class RealmKeysUtils @Inject constructor(context: Context, private fun extractKeyForDatabase(alias: String): ByteArray { val encryptedB64 = sharedPreferences.getString("${ENCRYPTED_KEY_PREFIX}_$alias", null) val encryptedKey = Base64.decode(encryptedB64, Base64.NO_PADDING) - val b64 = secretStoringUtils.loadSecureSecret(encryptedKey, alias) + val b64 = secretStoringUtils.loadSecureSecretBytes(encryptedKey, alias) return Base64.decode(b64, Base64.NO_PADDING) } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/RealmQueryLatch.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/RealmQueryLatch.kt index c9c797304a..c5ca861e9b 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/RealmQueryLatch.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/RealmQueryLatch.kt @@ -27,9 +27,11 @@ import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.withContext import kotlinx.coroutines.withTimeout -internal suspend fun awaitNotEmptyResult(realmConfiguration: RealmConfiguration, - timeoutMillis: Long, - builder: (Realm) -> RealmQuery) { +internal suspend fun awaitNotEmptyResult( + realmConfiguration: RealmConfiguration, + timeoutMillis: Long, + builder: (Realm) -> RealmQuery +) { withTimeout(timeoutMillis) { // Confine Realm interaction to a single thread with Looper. withContext(Dispatchers.Main) { 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 55bccfd1ec..665567bf2a 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 @@ -46,6 +46,9 @@ import org.matrix.android.sdk.internal.database.migration.MigrateSessionTo025 import org.matrix.android.sdk.internal.database.migration.MigrateSessionTo026 import org.matrix.android.sdk.internal.database.migration.MigrateSessionTo027 import org.matrix.android.sdk.internal.database.migration.MigrateSessionTo028 +import org.matrix.android.sdk.internal.database.migration.MigrateSessionTo029 +import org.matrix.android.sdk.internal.database.migration.MigrateSessionTo030 +import org.matrix.android.sdk.internal.database.migration.MigrateSessionTo031 import org.matrix.android.sdk.internal.util.Normalizer import timber.log.Timber import javax.inject.Inject @@ -60,7 +63,7 @@ internal class RealmSessionStoreMigration @Inject constructor( override fun equals(other: Any?) = other is RealmSessionStoreMigration override fun hashCode() = 1000 - val schemaVersion = 28L + val schemaVersion = 31L override fun migrate(realm: DynamicRealm, oldVersion: Long, newVersion: Long) { Timber.d("Migrating Realm Session from $oldVersion to $newVersion") @@ -93,5 +96,8 @@ internal class RealmSessionStoreMigration @Inject constructor( if (oldVersion < 26) MigrateSessionTo026(realm).perform() if (oldVersion < 27) MigrateSessionTo027(realm).perform() if (oldVersion < 28) MigrateSessionTo028(realm).perform() + if (oldVersion < 29) MigrateSessionTo029(realm).perform() + if (oldVersion < 30) MigrateSessionTo030(realm).perform() + if (oldVersion < 31) MigrateSessionTo031(realm).perform() } } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/SessionRealmConfigurationFactory.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/SessionRealmConfigurationFactory.kt index 08d55b5647..949dd5daa1 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/SessionRealmConfigurationFactory.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/SessionRealmConfigurationFactory.kt @@ -44,7 +44,8 @@ internal class SessionRealmConfigurationFactory @Inject constructor( @SessionFilesDirectory val directory: File, @SessionId val sessionId: String, @UserMd5 val userMd5: String, - context: Context) { + context: Context +) { // Keep legacy preferences name for compatibility reason private val sharedPreferences = context.getSharedPreferences("im.vector.matrix.android.realm", Context.MODE_PRIVATE) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/helper/ChunkEntityHelper.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/helper/ChunkEntityHelper.kt index caaf6b8110..234caec970 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/helper/ChunkEntityHelper.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/helper/ChunkEntityHelper.kt @@ -17,7 +17,6 @@ package org.matrix.android.sdk.internal.database.helper import io.realm.Realm -import io.realm.Sort import io.realm.kotlin.createObject import org.matrix.android.sdk.api.session.room.model.RoomMemberContent import org.matrix.android.sdk.internal.database.model.ChunkEntity @@ -34,32 +33,9 @@ import org.matrix.android.sdk.internal.database.model.TimelineEventEntityFields import org.matrix.android.sdk.internal.database.query.find import org.matrix.android.sdk.internal.database.query.getOrCreate import org.matrix.android.sdk.internal.database.query.where -import org.matrix.android.sdk.internal.database.query.whereRoomId -import org.matrix.android.sdk.internal.extensions.assertIsManaged import org.matrix.android.sdk.internal.session.room.timeline.PaginationDirection import timber.log.Timber -internal fun ChunkEntity.merge(roomId: String, chunkToMerge: ChunkEntity, direction: PaginationDirection) { - assertIsManaged() - val localRealm = this.realm - val eventsToMerge: List - if (direction == PaginationDirection.FORWARDS) { - this.nextToken = chunkToMerge.nextToken - this.isLastForward = chunkToMerge.isLastForward - eventsToMerge = chunkToMerge.timelineEvents.sort(TimelineEventEntityFields.DISPLAY_INDEX, Sort.ASCENDING) - } else { - this.prevToken = chunkToMerge.prevToken - this.isLastBackward = chunkToMerge.isLastBackward - eventsToMerge = chunkToMerge.timelineEvents.sort(TimelineEventEntityFields.DISPLAY_INDEX, Sort.DESCENDING) - } - chunkToMerge.stateEvents.forEach { stateEvent -> - addStateEvent(roomId, stateEvent, direction) - } - eventsToMerge.forEach { - addTimelineEventFromMerge(localRealm, it, direction) - } -} - internal fun ChunkEntity.addStateEvent(roomId: String, stateEvent: EventEntity, direction: PaginationDirection) { if (direction == PaginationDirection.BACKWARDS) { Timber.v("We don't keep chunk state events when paginating backward") @@ -79,11 +55,13 @@ internal fun ChunkEntity.addStateEvent(roomId: String, stateEvent: EventEntity, } } -internal fun ChunkEntity.addTimelineEvent(roomId: String, - eventEntity: EventEntity, - direction: PaginationDirection, - ownedByThreadChunk: Boolean = false, - roomMemberContentsByUser: Map? = null): TimelineEventEntity? { +internal fun ChunkEntity.addTimelineEvent( + roomId: String, + eventEntity: EventEntity, + direction: PaginationDirection, + ownedByThreadChunk: Boolean = false, + roomMemberContentsByUser: Map? = null +): TimelineEventEntity? { val eventId = eventEntity.eventId if (timelineEvents.find(eventId) != null) { return null @@ -142,40 +120,6 @@ internal fun computeIsUnique( } } -private fun ChunkEntity.addTimelineEventFromMerge(realm: Realm, timelineEventEntity: TimelineEventEntity, direction: PaginationDirection) { - val eventId = timelineEventEntity.eventId - if (timelineEvents.find(eventId) != null) { - return - } - val displayIndex = nextDisplayIndex(direction) - val localId = TimelineEventEntity.nextId(realm) - val copied = realm.createObject().apply { - this.localId = localId - this.root = timelineEventEntity.root - this.eventId = timelineEventEntity.eventId - this.roomId = timelineEventEntity.roomId - this.annotations = timelineEventEntity.annotations - this.readReceipts = timelineEventEntity.readReceipts - this.displayIndex = displayIndex - this.senderAvatar = timelineEventEntity.senderAvatar - this.senderName = timelineEventEntity.senderName - this.isUniqueDisplayName = timelineEventEntity.isUniqueDisplayName - } - handleThreadSummary(realm, eventId, copied) - timelineEvents.add(copied) -} - -/** - * Upon copy of the timeline events we should update the latestMessage TimelineEventEntity with the new one. - */ -private fun handleThreadSummary(realm: Realm, oldEventId: String, newTimelineEventEntity: TimelineEventEntity) { - EventEntity - .whereRoomId(realm, newTimelineEventEntity.roomId) - .equalTo(EventEntityFields.IS_ROOT_THREAD, true) - .equalTo(EventEntityFields.THREAD_SUMMARY_LATEST_MESSAGE.EVENT_ID, oldEventId) - .findFirst()?.threadSummaryLatestMessage = newTimelineEventEntity -} - private fun handleReadReceipts(realm: Realm, roomId: String, eventEntity: EventEntity, senderId: String): ReadReceiptsSummaryEntity { val readReceiptsSummaryEntity = ReadReceiptsSummaryEntity.where(realm, eventEntity.eventId).findFirst() ?: realm.createObject(eventEntity.eventId).apply { @@ -199,7 +143,7 @@ private fun handleReadReceipts(realm: Realm, roomId: String, eventEntity: EventE internal fun ChunkEntity.nextDisplayIndex(direction: PaginationDirection): Int { return when (direction) { - PaginationDirection.FORWARDS -> { + PaginationDirection.FORWARDS -> { (timelineEvents.where().max(TimelineEventEntityFields.DISPLAY_INDEX)?.toInt() ?: 0) + 1 } PaginationDirection.BACKWARDS -> { diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/helper/ThreadEventsHelper.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/helper/ThreadEventsHelper.kt index 5db859bce2..dfac7f6708 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/helper/ThreadEventsHelper.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/helper/ThreadEventsHelper.kt @@ -47,9 +47,11 @@ private typealias Summary = Pair? */ internal fun Map.updateThreadSummaryIfNeeded( roomId: String, - realm: Realm, currentUserId: String, + realm: Realm, + currentUserId: String, chunkEntity: ChunkEntity? = null, - shouldUpdateNotifications: Boolean = true) { + shouldUpdateNotifications: Boolean = true +) { for ((rootThreadEventId, eventEntity) in this) { eventEntity.threadSummaryInThread(eventEntity.realm, rootThreadEventId, chunkEntity)?.let { threadSummary -> @@ -87,7 +89,8 @@ internal fun EventEntity.findRootThreadEvent(): EventEntity? = */ internal fun EventEntity.markEventAsRoot( inThreadMessages: Int, - latestMessageTimelineEventEntity: TimelineEventEntity?) { + latestMessageTimelineEventEntity: TimelineEventEntity? +) { isRootThread = true numberOfThreads = inThreadMessages threadSummaryLatestMessage = latestMessageTimelineEventEntity 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 5b4fe287cb..0a6d4bf833 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 @@ -54,7 +54,8 @@ internal fun ThreadSummaryEntity.updateThreadSummary( numberOfThreads: Int?, latestThreadEventEntity: EventEntity?, isUserParticipating: Boolean, - roomMemberContentsByUser: HashMap) { + roomMemberContentsByUser: HashMap +) { updateThreadSummaryRootEvent(rootThreadEventEntity, roomMemberContentsByUser) updateThreadSummaryLatestEvent(latestThreadEventEntity, roomMemberContentsByUser) this.isUserParticipating = isUserParticipating @@ -180,7 +181,7 @@ internal fun ThreadSummaryEntity.Companion.createOrUpdate( roomEntity.addIfNecessary(threadSummary) } - ThreadSummaryUpdateType.ADD -> { + ThreadSummaryUpdateType.ADD -> { val rootThreadEventId = threadEventEntity?.rootThreadEventId ?: return Timber.i("###THREADS ThreadSummaryHelper ADD for root eventId:$rootThreadEventId") @@ -270,7 +271,7 @@ private fun HashMap.addSenderState(realm: Realm, roo * Create an EventEntity for the root thread event or get an existing one. */ private fun createEventEntity(realm: Realm, roomId: String, event: Event, currentTimeMillis: Long): EventEntity { - val ageLocalTs = event.unsignedData?.age?.let { currentTimeMillis - it } + val ageLocalTs = currentTimeMillis - (event.unsignedData?.age ?: 0) return event.toEntity(roomId, SendState.SYNCED, ageLocalTs).copyToRealmOrIgnore(realm, EventInsertType.PAGINATION) } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/mapper/DraftMapper.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/mapper/DraftMapper.kt index a00a2a8ee1..4cf2ef6723 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/mapper/DraftMapper.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/mapper/DraftMapper.kt @@ -27,21 +27,21 @@ internal object DraftMapper { fun map(entity: DraftEntity): UserDraft { return when (entity.draftMode) { DraftEntity.MODE_REGULAR -> UserDraft.Regular(entity.content) - DraftEntity.MODE_EDIT -> UserDraft.Edit(entity.linkedEventId, entity.content) - DraftEntity.MODE_QUOTE -> UserDraft.Quote(entity.linkedEventId, entity.content) - DraftEntity.MODE_REPLY -> UserDraft.Reply(entity.linkedEventId, entity.content) - DraftEntity.MODE_VOICE -> UserDraft.Voice(entity.content) - else -> null + DraftEntity.MODE_EDIT -> UserDraft.Edit(entity.linkedEventId, entity.content) + DraftEntity.MODE_QUOTE -> UserDraft.Quote(entity.linkedEventId, entity.content) + DraftEntity.MODE_REPLY -> UserDraft.Reply(entity.linkedEventId, entity.content) + DraftEntity.MODE_VOICE -> UserDraft.Voice(entity.content) + else -> null } ?: UserDraft.Regular("") } fun map(domain: UserDraft): DraftEntity { return when (domain) { is UserDraft.Regular -> DraftEntity(content = domain.content, draftMode = DraftEntity.MODE_REGULAR, linkedEventId = "") - is UserDraft.Edit -> DraftEntity(content = domain.content, draftMode = DraftEntity.MODE_EDIT, linkedEventId = domain.linkedEventId) - is UserDraft.Quote -> DraftEntity(content = domain.content, draftMode = DraftEntity.MODE_QUOTE, linkedEventId = domain.linkedEventId) - is UserDraft.Reply -> DraftEntity(content = domain.content, draftMode = DraftEntity.MODE_REPLY, linkedEventId = domain.linkedEventId) - is UserDraft.Voice -> DraftEntity(content = domain.content, draftMode = DraftEntity.MODE_VOICE, linkedEventId = "") + is UserDraft.Edit -> DraftEntity(content = domain.content, draftMode = DraftEntity.MODE_EDIT, linkedEventId = domain.linkedEventId) + is UserDraft.Quote -> DraftEntity(content = domain.content, draftMode = DraftEntity.MODE_QUOTE, linkedEventId = domain.linkedEventId) + is UserDraft.Reply -> DraftEntity(content = domain.content, draftMode = DraftEntity.MODE_REPLY, linkedEventId = domain.linkedEventId) + is UserDraft.Voice -> DraftEntity(content = domain.content, draftMode = DraftEntity.MODE_VOICE, linkedEventId = "") } } } 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 c747ad334f..6bbeb17fdd 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 @@ -58,7 +58,7 @@ internal object EventAnnotationsSummaryMapper { PollResponseAggregatedSummaryEntityMapper.map(it) }, liveLocationShareAggregatedSummary = annotationsSummary.liveLocationShareAggregatedSummary?.let { - LiveLocationShareAggregatedSummaryMapper.map(it) + 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 bc7d40bf6f..0f0a847c78 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 @@ -127,10 +127,12 @@ internal fun EventEntity.asDomain(castJsonNumbers: Boolean = false): Event { return EventMapper.map(this, castJsonNumbers) } -internal fun Event.toEntity(roomId: String, - sendState: SendState, - ageLocalTs: Long?, - contentToInject: String? = null): EventEntity { +internal fun Event.toEntity( + roomId: String, + sendState: SendState, + ageLocalTs: Long, + contentToInject: String? = null +): EventEntity { return EventMapper.map(this, roomId).apply { this.sendState = sendState this.ageLocalTs = ageLocalTs diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/mapper/HomeServerCapabilitiesMapper.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/mapper/HomeServerCapabilitiesMapper.kt index 20af43530c..184a0108b9 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/mapper/HomeServerCapabilitiesMapper.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/mapper/HomeServerCapabilitiesMapper.kt @@ -42,7 +42,8 @@ internal object HomeServerCapabilitiesMapper { lastVersionIdentityServerSupported = entity.lastVersionIdentityServerSupported, defaultIdentityServerUrl = entity.defaultIdentityServerUrl, roomVersions = mapRoomVersion(entity.roomVersionsJson), - canUseThreading = entity.canUseThreading + canUseThreading = entity.canUseThreading, + canControlLogoutDevices = entity.canControlLogoutDevices ) } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/mapper/IsUselessResolver.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/mapper/IsUselessResolver.kt index d704ecac8e..1a4821e80c 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/mapper/IsUselessResolver.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/mapper/IsUselessResolver.kt @@ -32,7 +32,7 @@ internal object IsUselessResolver { event.content != null && event.content.toContent() == event.resolvedPrevContent()?.toContent() } - else -> false + else -> false } } } 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 index 71b36f88bd..4a4c730a0b 100644 --- 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 @@ -16,15 +16,19 @@ package org.matrix.android.sdk.internal.database.mapper +import com.zhuinden.monarchy.Monarchy 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 +import javax.inject.Inject -internal object LiveLocationShareAggregatedSummaryMapper { +internal class LiveLocationShareAggregatedSummaryMapper @Inject constructor() : + Monarchy.Mapper { - fun map(entity: LiveLocationShareAggregatedSummaryEntity): LiveLocationShareAggregatedSummary { + override fun map(entity: LiveLocationShareAggregatedSummaryEntity): LiveLocationShareAggregatedSummary { return LiveLocationShareAggregatedSummary( + userId = entity.userId, 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/RoomSummaryMapper.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/mapper/RoomSummaryMapper.kt index 41faf30a82..735cfe411c 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/mapper/RoomSummaryMapper.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/mapper/RoomSummaryMapper.kt @@ -29,8 +29,10 @@ import org.matrix.android.sdk.internal.database.model.RoomSummaryEntity import org.matrix.android.sdk.internal.database.model.presence.toUserPresence import javax.inject.Inject -internal class RoomSummaryMapper @Inject constructor(private val timelineEventMapper: TimelineEventMapper, - private val typingUsersTracker: TypingUsersTracker) { +internal class RoomSummaryMapper @Inject constructor( + private val timelineEventMapper: TimelineEventMapper, + private val typingUsersTracker: TypingUsersTracker +) { fun map(roomSummaryEntity: RoomSummaryEntity): RoomSummary { val tags = roomSummaryEntity.tags().map { @@ -109,7 +111,7 @@ internal class RoomSummaryMapper @Inject constructor(private val timelineEventMa // I should probably use #hasEncryptorClassForAlgorithm but it says it supports // OLM which is some legacy? Now only megolm allowed in rooms MXCRYPTO_ALGORITHM_MEGOLM -> RoomEncryptionAlgorithm.Megolm - else -> RoomEncryptionAlgorithm.UnsupportedAlgorithm(alg) + else -> RoomEncryptionAlgorithm.UnsupportedAlgorithm(alg) } ) } 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 754a66bb4b..38de70cc7c 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 @@ -21,8 +21,10 @@ import org.matrix.android.sdk.internal.database.model.RoomSummaryEntityFields 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) { +internal class MigrateSessionTo019( + realm: DynamicRealm, + 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/MigrateSessionTo029.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/migration/MigrateSessionTo029.kt new file mode 100644 index 0000000000..17dc0f7c82 --- /dev/null +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/migration/MigrateSessionTo029.kt @@ -0,0 +1,37 @@ +/* + * 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.livelocation.LiveLocationShareAggregatedSummaryEntityFields +import org.matrix.android.sdk.internal.util.database.RealmMigrator + +/** + * Migrating to: + * Live location sharing aggregated summary: adding new field userId. + */ +internal class MigrateSessionTo029(realm: DynamicRealm) : RealmMigrator(realm, 29) { + + override fun doMigrate(realm: DynamicRealm) { + realm.schema.get("LiveLocationShareAggregatedSummaryEntity") + ?.addField(LiveLocationShareAggregatedSummaryEntityFields.USER_ID, String::class.java, FieldAttribute.REQUIRED) + ?.transform { obj -> + obj.setString(LiveLocationShareAggregatedSummaryEntityFields.USER_ID, "") + } + } +} diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/migration/MigrateSessionTo030.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/migration/MigrateSessionTo030.kt new file mode 100644 index 0000000000..5d24b1433c --- /dev/null +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/migration/MigrateSessionTo030.kt @@ -0,0 +1,61 @@ +/* + * Copyright (c) 2022 The Matrix.org Foundation C.I.C. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.matrix.android.sdk.internal.database.migration + +import io.realm.DynamicRealm +import org.matrix.android.sdk.internal.database.model.ChunkEntityFields +import org.matrix.android.sdk.internal.database.model.EventEntityFields +import org.matrix.android.sdk.internal.database.model.TimelineEventEntityFields +import org.matrix.android.sdk.internal.util.database.RealmMigrator +import timber.log.Timber + +/** + * Migrating to: + * Cleaning old chunks which may have broken links. + */ +internal class MigrateSessionTo030(realm: DynamicRealm) : RealmMigrator(realm, 30) { + + override fun doMigrate(realm: DynamicRealm) { + // Delete all previous chunks + val chunks = realm.where("ChunkEntity") + .equalTo(ChunkEntityFields.IS_LAST_FORWARD, false) + .findAll() + + val nbOfDeletedChunks = chunks.size + var nbOfDeletedTimelineEvents = 0 + var nbOfDeletedEvents = 0 + chunks.forEach { chunk -> + val timelineEvents = chunk.getList(ChunkEntityFields.TIMELINE_EVENTS.`$`) + timelineEvents.forEach { timelineEvent -> + // Don't delete state events + val event = timelineEvent.getObject(TimelineEventEntityFields.ROOT.`$`) + if (event?.isNull(EventEntityFields.STATE_KEY) == true) { + nbOfDeletedEvents++ + event.deleteFromRealm() + } + } + nbOfDeletedTimelineEvents += timelineEvents.size + timelineEvents.deleteAllFromRealm() + } + chunks.deleteAllFromRealm() + Timber.d( + "MigrateSessionTo030: $nbOfDeletedChunks deleted chunk(s)," + + " $nbOfDeletedTimelineEvents deleted TimelineEvent(s)" + + " and $nbOfDeletedEvents deleted Event(s)." + ) + } +} diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/migration/MigrateSessionTo031.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/migration/MigrateSessionTo031.kt new file mode 100644 index 0000000000..e278b74756 --- /dev/null +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/migration/MigrateSessionTo031.kt @@ -0,0 +1,31 @@ +/* + * Copyright (c) 2022 The Matrix.org Foundation C.I.C. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.matrix.android.sdk.internal.database.migration + +import io.realm.DynamicRealm +import org.matrix.android.sdk.internal.database.model.HomeServerCapabilitiesEntityFields +import org.matrix.android.sdk.internal.extensions.forceRefreshOfHomeServerCapabilities +import org.matrix.android.sdk.internal.util.database.RealmMigrator + +internal class MigrateSessionTo031(realm: DynamicRealm) : RealmMigrator(realm, 31) { + + override fun doMigrate(realm: DynamicRealm) { + realm.schema.get("HomeServerCapabilitiesEntity") + ?.addField(HomeServerCapabilitiesEntityFields.CAN_CONTROL_LOGOUT_DEVICES, Boolean::class.java) + ?.forceRefreshOfHomeServerCapabilities() + } +} 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 8399d82d5d..e5360ce0b6 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 @@ -53,7 +53,8 @@ internal open class ChunkEntity( internal fun ChunkEntity.deleteOnCascade( deleteStateEvents: Boolean, - canDeleteRoot: Boolean) { + canDeleteRoot: Boolean +) { assertIsManaged() if (deleteStateEvents) { stateEvents.deleteAllFromRealm() diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/CurrentStateEventEntity.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/CurrentStateEventEntity.kt index 251b057722..9deef69dab 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/CurrentStateEventEntity.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/CurrentStateEventEntity.kt @@ -20,11 +20,12 @@ package org.matrix.android.sdk.internal.database.model import io.realm.RealmObject import io.realm.annotations.Index -internal open class CurrentStateEventEntity(var eventId: String = "", - var root: EventEntity? = null, - @Index var roomId: String = "", - @Index var type: String = "", - @Index var stateKey: String = "" +internal open class CurrentStateEventEntity( + var eventId: String = "", + var root: EventEntity? = null, + @Index var roomId: String = "", + @Index var type: String = "", + @Index var stateKey: String = "" ) : RealmObject() { companion object } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/DraftEntity.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/DraftEntity.kt index fd09da4448..3c19d4d443 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/DraftEntity.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/DraftEntity.kt @@ -18,9 +18,10 @@ package org.matrix.android.sdk.internal.database.model import io.realm.RealmObject -internal open class DraftEntity(var content: String = "", - var draftMode: String = MODE_REGULAR, - var linkedEventId: String = "" +internal open class DraftEntity( + var content: String = "", + var draftMode: String = MODE_REGULAR, + var linkedEventId: String = "" ) : RealmObject() { companion object { diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/EventEntity.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/EventEntity.kt index ba80cc8302..8b5a211fba 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/EventEntity.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/EventEntity.kt @@ -25,28 +25,29 @@ import org.matrix.android.sdk.api.session.threads.ThreadNotificationState import org.matrix.android.sdk.internal.di.MoshiProvider import org.matrix.android.sdk.internal.extensions.assertIsManaged -internal open class EventEntity(@Index var eventId: String = "", - @Index var roomId: String = "", - @Index var type: String = "", - var content: String? = null, - var prevContent: String? = null, - var isUseless: Boolean = false, - @Index var stateKey: String? = null, - var originServerTs: Long? = null, - @Index var sender: String? = null, +internal open class EventEntity( + @Index var eventId: String = "", + @Index var roomId: String = "", + @Index var type: String = "", + var content: String? = null, + var prevContent: String? = null, + var isUseless: Boolean = false, + @Index var stateKey: String? = null, + var originServerTs: Long? = null, + @Index var sender: String? = null, // Can contain a serialized MatrixError - var sendStateDetails: String? = null, - var age: Long? = 0, - var unsignedData: String? = null, - var redacts: String? = null, - var decryptionResultJson: String? = null, - var ageLocalTs: Long? = null, + var sendStateDetails: String? = null, + var age: Long? = 0, + var unsignedData: String? = null, + var redacts: String? = null, + var decryptionResultJson: String? = null, + var ageLocalTs: Long? = null, // Thread related, no need to create a new Entity for performance - @Index var isRootThread: Boolean = false, - @Index var rootThreadEventId: String? = null, + @Index var isRootThread: Boolean = false, + @Index var rootThreadEventId: String? = null, // Number messages within the thread - var numberOfThreads: Int = 0, - var threadSummaryLatestMessage: TimelineEventEntity? = null + var numberOfThreads: Int = 0, + var threadSummaryLatestMessage: TimelineEventEntity? = null ) : RealmObject() { private var sendStateStr: String = SendState.UNKNOWN.name diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/EventInsertEntity.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/EventInsertEntity.kt index 5cfd306d2f..eff332dc3a 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/EventInsertEntity.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/EventInsertEntity.kt @@ -22,13 +22,14 @@ import io.realm.RealmObject * This class is used to get notification on new events being inserted. It's to avoid realm getting slow when listening to insert * in EventEntity table. */ -internal open class EventInsertEntity(var eventId: String = "", - var eventType: String = "", - /** - * This flag will be used to filter EventInsertEntity in EventInsertLiveObserver. - * Currently it's set to false when the event content is encrypted. - */ - var canBeProcessed: Boolean = true +internal open class EventInsertEntity( + var eventId: String = "", + var eventType: String = "", + /** + * This flag will be used to filter EventInsertEntity in EventInsertLiveObserver. + * Currently it's set to false when the event content is encrypted. + */ + var canBeProcessed: Boolean = true ) : RealmObject() { private var insertTypeStr: String = EventInsertType.INCREMENTAL_SYNC.name diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/GroupSummaryEntity.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/GroupSummaryEntity.kt index 4ba45dcda2..d965148559 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/GroupSummaryEntity.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/GroupSummaryEntity.kt @@ -21,12 +21,13 @@ import io.realm.RealmObject import io.realm.annotations.PrimaryKey import org.matrix.android.sdk.api.session.room.model.Membership -internal open class GroupSummaryEntity(@PrimaryKey var groupId: String = "", - var displayName: String = "", - var shortDescription: String = "", - var avatarUrl: String = "", - var roomIds: RealmList = RealmList(), - var userIds: RealmList = RealmList() +internal open class GroupSummaryEntity( + @PrimaryKey var groupId: String = "", + var displayName: String = "", + var shortDescription: String = "", + var avatarUrl: String = "", + var roomIds: RealmList = RealmList(), + var userIds: RealmList = RealmList() ) : RealmObject() { private var membershipStr: String = Membership.NONE.name diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/HomeServerCapabilitiesEntity.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/HomeServerCapabilitiesEntity.kt index 47a83f0ed9..9d90973f8a 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/HomeServerCapabilitiesEntity.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/HomeServerCapabilitiesEntity.kt @@ -29,7 +29,8 @@ internal open class HomeServerCapabilitiesEntity( var lastVersionIdentityServerSupported: Boolean = false, var defaultIdentityServerUrl: String? = null, var lastUpdatedTimestamp: Long = 0L, - var canUseThreading: Boolean = false + var canUseThreading: Boolean = false, + var canControlLogoutDevices: Boolean = false ) : RealmObject() { companion object diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/ReadReceiptEntity.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/ReadReceiptEntity.kt index e01d849cc2..9623c95359 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/ReadReceiptEntity.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/ReadReceiptEntity.kt @@ -21,11 +21,12 @@ import io.realm.RealmResults import io.realm.annotations.LinkingObjects import io.realm.annotations.PrimaryKey -internal open class ReadReceiptEntity(@PrimaryKey var primaryKey: String = "", - var eventId: String = "", - var roomId: String = "", - var userId: String = "", - var originServerTs: Double = 0.0 +internal open class ReadReceiptEntity( + @PrimaryKey var primaryKey: String = "", + var eventId: String = "", + var roomId: String = "", + var userId: String = "", + var originServerTs: Double = 0.0 ) : RealmObject() { companion object 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 d8e6b8af0f..d2116fa9b8 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 @@ -24,11 +24,12 @@ import org.matrix.android.sdk.internal.database.model.threads.ThreadSummaryEntit import org.matrix.android.sdk.internal.database.query.findRootOrLatest import org.matrix.android.sdk.internal.extensions.assertIsManaged -internal open class RoomEntity(@PrimaryKey var roomId: String = "", - var chunks: RealmList = RealmList(), - var sendingTimelineEvents: RealmList = RealmList(), - var threadSummaries: RealmList = RealmList(), - var accountData: RealmList = RealmList() +internal open class RoomEntity( + @PrimaryKey var roomId: String = "", + var chunks: RealmList = RealmList(), + var sendingTimelineEvents: RealmList = RealmList(), + var threadSummaries: RealmList = RealmList(), + var accountData: RealmList = RealmList() ) : RealmObject() { private var membershipStr: String = Membership.NONE.name diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/RoomMemberSummaryEntity.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/RoomMemberSummaryEntity.kt index a8a76d1681..6fb9324fcd 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/RoomMemberSummaryEntity.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/RoomMemberSummaryEntity.kt @@ -23,13 +23,14 @@ import org.matrix.android.sdk.api.session.room.model.Membership import org.matrix.android.sdk.api.util.MatrixItem import org.matrix.android.sdk.internal.database.model.presence.UserPresenceEntity -internal open class RoomMemberSummaryEntity(@PrimaryKey var primaryKey: String = "", - @Index var userId: String = "", - @Index var roomId: String = "", - @Index var displayName: String? = null, - var avatarUrl: String? = null, - var reason: String? = null, - var isDirect: Boolean = false +internal open class RoomMemberSummaryEntity( + @PrimaryKey var primaryKey: String = "", + @Index var userId: String = "", + @Index var roomId: String = "", + @Index var displayName: String? = null, + var avatarUrl: String? = null, + var reason: String? = null, + var isDirect: Boolean = false ) : RealmObject() { private var membershipStr: String = Membership.NONE.name diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/SyncEntity.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/SyncEntity.kt index a82ec41456..ff261b80c1 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/SyncEntity.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/SyncEntity.kt @@ -19,6 +19,7 @@ package org.matrix.android.sdk.internal.database.model import io.realm.RealmObject import io.realm.annotations.PrimaryKey -internal open class SyncEntity(var nextBatch: String? = null, - @PrimaryKey var id: Long = 0 +internal open class SyncEntity( + var nextBatch: String? = null, + @PrimaryKey var id: Long = 0 ) : RealmObject() 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 477c04fe51..c8f22dc2cc 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 @@ -22,20 +22,21 @@ import io.realm.annotations.Index import io.realm.annotations.LinkingObjects import org.matrix.android.sdk.internal.extensions.assertIsManaged -internal open class TimelineEventEntity(var localId: Long = 0, - @Index var eventId: String = "", - @Index var roomId: String = "", - @Index var displayIndex: Int = 0, - var root: EventEntity? = null, - var annotations: EventAnnotationsSummaryEntity? = null, - var senderName: String? = null, - var isUniqueDisplayName: Boolean = false, - var senderAvatar: String? = null, - var senderMembershipEventId: String? = null, +internal open class TimelineEventEntity( + var localId: Long = 0, + @Index var eventId: String = "", + @Index var roomId: String = "", + @Index var displayIndex: Int = 0, + var root: EventEntity? = null, + var annotations: EventAnnotationsSummaryEntity? = null, + var senderName: String? = null, + 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. - var ownedByThreadChunk: Boolean = false, - var readReceipts: ReadReceiptsSummaryEntity? = null + var ownedByThreadChunk: Boolean = false, + var readReceipts: ReadReceiptsSummaryEntity? = null ) : RealmObject() { @LinkingObjects("timelineEvents") diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/UserDraftsEntity.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/UserDraftsEntity.kt index 2c778f9797..7ccfc6b104 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/UserDraftsEntity.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/UserDraftsEntity.kt @@ -24,7 +24,8 @@ import io.realm.annotations.LinkingObjects /** * Create a specific table to be able to do direct query on it and keep the draft ordered. */ -internal open class UserDraftsEntity(var userDrafts: RealmList = RealmList() +internal open class UserDraftsEntity( + var userDrafts: RealmList = RealmList() ) : RealmObject() { // Link to RoomSummaryEntity diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/UserEntity.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/UserEntity.kt index bd6e73582d..3644d12ac9 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/UserEntity.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/UserEntity.kt @@ -19,9 +19,10 @@ package org.matrix.android.sdk.internal.database.model import io.realm.RealmObject import io.realm.annotations.PrimaryKey -internal open class UserEntity(@PrimaryKey var userId: String = "", - var displayName: String = "", - var avatarUrl: String = "" +internal open class UserEntity( + @PrimaryKey var userId: String = "", + var displayName: String = "", + var avatarUrl: String = "" ) : RealmObject() { companion object 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 index 4d0d2c5c64..c5df8e9338 100644 --- 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 @@ -31,6 +31,11 @@ internal open class LiveLocationShareAggregatedSummaryEntity( var roomId: String = "", + var userId: String = "", + + /** + * Indicate whether the live is currently running. + */ var isActive: Boolean? = null, var endOfLiveTimestampMillis: Long? = null, diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/presence/UserPresenceEntity.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/presence/UserPresenceEntity.kt index 5713337ec5..8d808417c2 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/presence/UserPresenceEntity.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/presence/UserPresenceEntity.kt @@ -21,12 +21,13 @@ import io.realm.annotations.PrimaryKey import org.matrix.android.sdk.api.session.presence.model.PresenceEnum import org.matrix.android.sdk.api.session.presence.model.UserPresence -internal open class UserPresenceEntity(@PrimaryKey var userId: String = "", - var lastActiveAgo: Long? = null, - var statusMessage: String? = null, - var isCurrentlyActive: Boolean? = null, - var avatarUrl: String? = null, - var displayName: String? = null +internal open class UserPresenceEntity( + @PrimaryKey var userId: String = "", + var lastActiveAgo: Long? = null, + var statusMessage: String? = null, + var isCurrentlyActive: Boolean? = null, + var avatarUrl: String? = null, + var displayName: String? = null ) : RealmObject() { var presence: PresenceEnum diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/threads/ThreadSummaryEntity.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/threads/ThreadSummaryEntity.kt index ab9d66548e..45f9e3aa20 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/threads/ThreadSummaryEntity.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/threads/ThreadSummaryEntity.kt @@ -23,17 +23,18 @@ import io.realm.annotations.LinkingObjects import org.matrix.android.sdk.internal.database.model.EventEntity import org.matrix.android.sdk.internal.database.model.RoomEntity -internal open class ThreadSummaryEntity(@Index var rootThreadEventId: String? = "", - var rootThreadEventEntity: EventEntity? = null, - var latestThreadEventEntity: EventEntity? = null, - var rootThreadSenderName: String? = null, - var latestThreadSenderName: String? = null, - var rootThreadSenderAvatar: String? = null, - var latestThreadSenderAvatar: String? = null, - var rootThreadIsUniqueDisplayName: Boolean = false, - var isUserParticipating: Boolean = false, - var latestThreadIsUniqueDisplayName: Boolean = false, - var numberOfThreads: Int = 0 +internal open class ThreadSummaryEntity( + @Index var rootThreadEventId: String? = "", + var rootThreadEventEntity: EventEntity? = null, + var latestThreadEventEntity: EventEntity? = null, + var rootThreadSenderName: String? = null, + var latestThreadSenderName: String? = null, + var rootThreadSenderAvatar: String? = null, + var latestThreadSenderAvatar: String? = null, + var rootThreadIsUniqueDisplayName: Boolean = false, + var isUserParticipating: Boolean = false, + var latestThreadIsUniqueDisplayName: Boolean = false, + var numberOfThreads: Int = 0 ) : RealmObject() { @LinkingObjects("threadSummaries") 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 9350102137..1e5d96b496 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 @@ -31,6 +31,7 @@ internal fun ChunkEntity.Companion.where(realm: Realm, roomId: String): RealmQue internal fun ChunkEntity.Companion.find(realm: Realm, roomId: String, prevToken: String? = null, nextToken: String? = null): ChunkEntity? { val query = where(realm, roomId) + if (prevToken == null && nextToken == null) return null if (prevToken != null) { query.equalTo(ChunkEntityFields.PREV_TOKEN, prevToken) } @@ -40,7 +41,7 @@ internal fun ChunkEntity.Companion.find(realm: Realm, roomId: String, prevToken: return query.findFirst() } -internal fun ChunkEntity.Companion.findAll(realm: Realm, roomId: String, prevToken: String? = null, nextToken: String? = null): RealmResults? { +internal fun ChunkEntity.Companion.findAll(realm: Realm, roomId: String, prevToken: String? = null, nextToken: String? = null): RealmResults { val query = where(realm, roomId) if (prevToken != null) { query.equalTo(ChunkEntityFields.PREV_TOKEN, prevToken) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/query/CurrentStateEventEntityQueries.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/query/CurrentStateEventEntityQueries.kt index 716783f2ba..e0dbf2eee8 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/query/CurrentStateEventEntityQueries.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/query/CurrentStateEventEntityQueries.kt @@ -23,40 +23,50 @@ import io.realm.kotlin.createObject import org.matrix.android.sdk.internal.database.model.CurrentStateEventEntity import org.matrix.android.sdk.internal.database.model.CurrentStateEventEntityFields -internal fun CurrentStateEventEntity.Companion.whereType(realm: Realm, - roomId: String, - type: String): RealmQuery { +internal fun CurrentStateEventEntity.Companion.whereType( + realm: Realm, + roomId: String, + type: String +): RealmQuery { return realm.where(CurrentStateEventEntity::class.java) .equalTo(CurrentStateEventEntityFields.ROOM_ID, roomId) .equalTo(CurrentStateEventEntityFields.TYPE, type) } -internal fun CurrentStateEventEntity.Companion.whereStateKey(realm: Realm, - roomId: String, - type: String, - stateKey: String): RealmQuery { +internal fun CurrentStateEventEntity.Companion.whereStateKey( + realm: Realm, + roomId: String, + type: String, + stateKey: String +): RealmQuery { return whereType(realm = realm, roomId = roomId, type = type) .equalTo(CurrentStateEventEntityFields.STATE_KEY, stateKey) } -internal fun CurrentStateEventEntity.Companion.getOrNull(realm: Realm, - roomId: String, - stateKey: String, - type: String): CurrentStateEventEntity? { +internal fun CurrentStateEventEntity.Companion.getOrNull( + realm: Realm, + roomId: String, + stateKey: String, + type: String +): CurrentStateEventEntity? { return whereStateKey(realm = realm, roomId = roomId, type = type, stateKey = stateKey).findFirst() } -internal fun CurrentStateEventEntity.Companion.getOrCreate(realm: Realm, - roomId: String, - stateKey: String, - type: String): CurrentStateEventEntity { +internal fun CurrentStateEventEntity.Companion.getOrCreate( + realm: Realm, + roomId: String, + stateKey: String, + type: String +): CurrentStateEventEntity { return getOrNull(realm = realm, roomId = roomId, stateKey = stateKey, type = type) ?: create(realm, roomId, stateKey, type) } -private fun create(realm: Realm, - roomId: String, - stateKey: String, - type: String): CurrentStateEventEntity { +private fun create( + realm: Realm, + roomId: String, + stateKey: String, + type: String +): CurrentStateEventEntity { return realm.createObject().apply { this.type = type this.roomId = roomId diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/query/EventEntityQueries.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/query/EventEntityQueries.kt index f7fa1037ba..0f1c226044 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/query/EventEntityQueries.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/query/EventEntityQueries.kt @@ -59,9 +59,10 @@ internal fun EventEntity.Companion.where(realm: Realm, eventIds: List): .`in`(EventEntityFields.EVENT_ID, eventIds.toTypedArray()) } -internal fun EventEntity.Companion.whereType(realm: Realm, - type: String, - roomId: String? = null +internal fun EventEntity.Companion.whereType( + realm: Realm, + type: String, + roomId: String? = null ): RealmQuery { val query = realm.where() if (roomId != null) { @@ -70,9 +71,11 @@ internal fun EventEntity.Companion.whereType(realm: Realm, return query.equalTo(EventEntityFields.TYPE, type) } -internal fun EventEntity.Companion.whereTypes(realm: Realm, - typeList: List = emptyList(), - roomId: String? = null): RealmQuery { +internal fun EventEntity.Companion.whereTypes( + realm: Realm, + typeList: List = emptyList(), + roomId: String? = null +): RealmQuery { val query = realm.where() query.`in`(EventEntityFields.TYPE, typeList.toTypedArray()) if (roomId != null) { 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 index 2e2e939fa2..d69f251f6f 100644 --- 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 @@ -27,10 +27,18 @@ internal fun LiveLocationShareAggregatedSummaryEntity.Companion.where( realm: Realm, roomId: String, eventId: String, +): RealmQuery { + return LiveLocationShareAggregatedSummaryEntity + .whereRoomId(realm, roomId = roomId) + .equalTo(LiveLocationShareAggregatedSummaryEntityFields.EVENT_ID, eventId) +} + +internal fun LiveLocationShareAggregatedSummaryEntity.Companion.whereRoomId( + realm: Realm, + roomId: String ): RealmQuery { return realm.where() .equalTo(LiveLocationShareAggregatedSummaryEntityFields.ROOM_ID, roomId) - .equalTo(LiveLocationShareAggregatedSummaryEntityFields.EVENT_ID, eventId) } internal fun LiveLocationShareAggregatedSummaryEntity.Companion.create( @@ -55,3 +63,40 @@ internal fun LiveLocationShareAggregatedSummaryEntity.Companion.getOrCreate( return LiveLocationShareAggregatedSummaryEntity.where(realm, roomId, eventId).findFirst() ?: LiveLocationShareAggregatedSummaryEntity.create(realm, roomId, eventId) } + +internal fun LiveLocationShareAggregatedSummaryEntity.Companion.get( + realm: Realm, + roomId: String, + eventId: String, +): LiveLocationShareAggregatedSummaryEntity? { + return LiveLocationShareAggregatedSummaryEntity.where(realm, roomId, eventId).findFirst() +} + +internal fun LiveLocationShareAggregatedSummaryEntity.Companion.findActiveLiveInRoomForUser( + realm: Realm, + roomId: String, + userId: String, + ignoredEventId: String, +): List { + return LiveLocationShareAggregatedSummaryEntity + .whereRoomId(realm, roomId = roomId) + .equalTo(LiveLocationShareAggregatedSummaryEntityFields.USER_ID, userId) + .equalTo(LiveLocationShareAggregatedSummaryEntityFields.IS_ACTIVE, true) + .notEqualTo(LiveLocationShareAggregatedSummaryEntityFields.EVENT_ID, ignoredEventId) + .findAll() + .toList() +} + +/** + * A live is considered as running when active and with at least a last known location. + */ +internal fun LiveLocationShareAggregatedSummaryEntity.Companion.findRunningLiveInRoom( + realm: Realm, + roomId: String, +): RealmQuery { + return LiveLocationShareAggregatedSummaryEntity + .whereRoomId(realm, roomId = roomId) + .equalTo(LiveLocationShareAggregatedSummaryEntityFields.IS_ACTIVE, true) + .isNotEmpty(LiveLocationShareAggregatedSummaryEntityFields.USER_ID) + .isNotNull(LiveLocationShareAggregatedSummaryEntityFields.LAST_LOCATION_CONTENT) +} 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 3cea19a690..67fd8296d5 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 @@ -26,8 +26,10 @@ import org.matrix.android.sdk.internal.database.model.PushRulesEntityFields import org.matrix.android.sdk.internal.database.model.PusherEntity import org.matrix.android.sdk.internal.database.model.PusherEntityFields -internal fun PusherEntity.Companion.where(realm: Realm, - pushKey: String? = null): RealmQuery { +internal fun PusherEntity.Companion.where( + realm: Realm, + pushKey: String? = null +): RealmQuery { return realm.where() .apply { if (pushKey != null) { @@ -36,17 +38,21 @@ internal fun PusherEntity.Companion.where(realm: Realm, } } -internal fun PushRulesEntity.Companion.where(realm: Realm, - scope: String, - kind: RuleKind): RealmQuery { +internal fun PushRulesEntity.Companion.where( + realm: Realm, + scope: String, + kind: RuleKind +): RealmQuery { return realm.where() .equalTo(PushRulesEntityFields.SCOPE, scope) .equalTo(PushRulesEntityFields.KIND_STR, kind.name) } -internal fun PushRuleEntity.Companion.where(realm: Realm, - scope: String, - ruleId: String): RealmQuery { +internal fun PushRuleEntity.Companion.where( + realm: Realm, + scope: String, + ruleId: String +): RealmQuery { return realm.where() .equalTo(PushRuleEntityFields.PARENT.SCOPE, scope) .equalTo(PushRuleEntityFields.RULE_ID, ruleId) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/query/ReadQueries.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/query/ReadQueries.kt index 1a832a29bb..0b0f01a67d 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/query/ReadQueries.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/query/ReadQueries.kt @@ -24,10 +24,12 @@ import org.matrix.android.sdk.internal.database.model.ReadMarkerEntity import org.matrix.android.sdk.internal.database.model.ReadReceiptEntity import org.matrix.android.sdk.internal.database.model.TimelineEventEntity -internal fun isEventRead(realmConfiguration: RealmConfiguration, - userId: String?, - roomId: String?, - eventId: String?): Boolean { +internal fun isEventRead( + realmConfiguration: RealmConfiguration, + userId: String?, + roomId: String?, + eventId: String? +): Boolean { if (userId.isNullOrBlank() || roomId.isNullOrBlank() || eventId.isNullOrBlank()) { return false } @@ -39,12 +41,12 @@ internal fun isEventRead(realmConfiguration: RealmConfiguration, val eventToCheck = TimelineEventEntity.where(realm, roomId, eventId).findFirst() when { // The event doesn't exist locally, let's assume it hasn't been read - eventToCheck == null -> false - eventToCheck.root?.sender == userId -> true + eventToCheck == null -> false + eventToCheck.root?.sender == userId -> true // If new event exists and the latest event is from ourselves we can infer the event is read - latestEventIsFromSelf(realm, roomId, userId) -> true + latestEventIsFromSelf(realm, roomId, userId) -> true eventToCheck.isBeforeLatestReadReceipt(realm, roomId, userId) -> true - else -> false + else -> false } } } @@ -77,9 +79,11 @@ private fun Realm.hasReadReceiptInLatestChunk(latestChunkEntity: ChunkEntity, ro } != null } -internal fun isReadMarkerMoreRecent(realmConfiguration: RealmConfiguration, - roomId: String?, - eventId: String?): Boolean { +internal fun isReadMarkerMoreRecent( + realmConfiguration: RealmConfiguration, + roomId: String?, + eventId: String? +): Boolean { if (roomId.isNullOrBlank() || eventId.isNullOrBlank()) { return false } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/query/RoomSummaryEntityQueries.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/query/RoomSummaryEntityQueries.kt index 8993c36a30..3c8d61432a 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/query/RoomSummaryEntityQueries.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/query/RoomSummaryEntityQueries.kt @@ -53,8 +53,10 @@ internal fun RoomSummaryEntity.Companion.getOrNull(realm: Realm, roomId: String) return where(realm, roomId).findFirst() } -internal fun RoomSummaryEntity.Companion.getDirectRooms(realm: Realm, - excludeRoomIds: Set? = null): RealmResults { +internal fun RoomSummaryEntity.Companion.getDirectRooms( + realm: Realm, + excludeRoomIds: Set? = null +): RealmResults { return RoomSummaryEntity.where(realm) .equalTo(RoomSummaryEntityFields.IS_DIRECT, true) .apply { 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 1654a33806..30010f90fd 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 @@ -33,39 +33,49 @@ internal fun TimelineEventEntity.Companion.where(realm: Realm): RealmQuery { +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 { +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 { +internal fun TimelineEventEntity.Companion.whereRoomId( + realm: Realm, + roomId: String +): RealmQuery { return where(realm) .equalTo(TimelineEventEntityFields.ROOM_ID, roomId) } -internal fun TimelineEventEntity.Companion.findWithSenderMembershipEvent(realm: Realm, - senderMembershipEventId: String): List { +internal fun TimelineEventEntity.Companion.findWithSenderMembershipEvent( + realm: Realm, + senderMembershipEventId: String +): List { return where(realm) .equalTo(TimelineEventEntityFields.SENDER_MEMBERSHIP_EVENT_ID, senderMembershipEventId) .findAll() } -internal fun TimelineEventEntity.Companion.latestEvent(realm: Realm, - roomId: String, - includesSending: Boolean, - filters: TimelineEventFilters = TimelineEventFilters()): TimelineEventEntity? { +internal fun TimelineEventEntity.Companion.latestEvent( + realm: Realm, + roomId: String, + includesSending: Boolean, + filters: TimelineEventFilters = TimelineEventFilters() +): TimelineEventEntity? { val roomEntity = RoomEntity.where(realm, roomId).findFirst() ?: return null val sendingTimelineEvents = roomEntity.sendingTimelineEvents.where().filterEvents(filters) @@ -129,9 +139,11 @@ internal fun RealmList.find(eventId: String): TimelineEvent .findFirst() } -internal fun TimelineEventEntity.Companion.findAllInRoomWithSendStates(realm: Realm, - roomId: String, - sendStates: List): RealmResults { +internal fun TimelineEventEntity.Companion.findAllInRoomWithSendStates( + realm: Realm, + roomId: String, + sendStates: List +): RealmResults { return whereRoomId(realm, roomId) .filterSendStates(sendStates) .findAll() @@ -145,8 +157,10 @@ internal fun RealmQuery.filterSendStates(sendStates: List): RealmResults { +internal fun TimelineEventEntity.Companion.findAllFrom( + realm: Realm, + senderIds: Collection +): RealmResults { return where(realm) .`in`(TimelineEventEntityFields.ROOT.SENDER, senderIds.toTypedArray()) .isNull(TimelineEventEntityFields.ROOT.STATE_KEY) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/debug/DebugModule.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/debug/DebugModule.kt new file mode 100644 index 0000000000..f392d39d7b --- /dev/null +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/debug/DebugModule.kt @@ -0,0 +1,29 @@ +/* + * 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.debug + +import dagger.Binds +import dagger.Module +import org.matrix.android.sdk.api.debug.DebugService + +@Module +internal abstract class DebugModule { + + @Binds + abstract fun bindDebugService(service: DefaultDebugService): DebugService +} diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/debug/DefaultDebugService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/debug/DefaultDebugService.kt new file mode 100644 index 0000000000..3f2e6fafc8 --- /dev/null +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/debug/DefaultDebugService.kt @@ -0,0 +1,44 @@ +/* + * 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.debug + +import io.realm.RealmConfiguration +import org.matrix.android.sdk.api.debug.DebugService +import org.matrix.android.sdk.internal.SessionManager +import org.matrix.android.sdk.internal.database.tools.RealmDebugTools +import org.matrix.android.sdk.internal.di.AuthDatabase +import org.matrix.android.sdk.internal.di.GlobalDatabase +import javax.inject.Inject + +internal class DefaultDebugService @Inject constructor( + @AuthDatabase private val realmConfigurationAuth: RealmConfiguration, + @GlobalDatabase private val realmConfigurationGlobal: RealmConfiguration, + private val sessionManager: SessionManager, +) : DebugService { + + override fun getAllRealmConfigurations(): List { + return sessionManager.getLastSession()?.getRealmConfigurations().orEmpty() + + realmConfigurationAuth + + realmConfigurationGlobal + } + + override fun logDbUsageInfo() { + RealmDebugTools(realmConfigurationAuth).logInfo("Auth") + RealmDebugTools(realmConfigurationGlobal).logInfo("Global") + sessionManager.getLastSession()?.logDbUsageInfo() + } +} diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/di/MatrixComponent.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/di/MatrixComponent.kt index dbc6aac6b5..44ec90ed40 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/di/MatrixComponent.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/di/MatrixComponent.kt @@ -28,10 +28,13 @@ import org.matrix.android.sdk.api.MatrixCoroutineDispatchers import org.matrix.android.sdk.api.auth.AuthenticationService import org.matrix.android.sdk.api.auth.HomeServerHistoryService import org.matrix.android.sdk.api.raw.RawService +import org.matrix.android.sdk.api.securestorage.SecureStorageModule +import org.matrix.android.sdk.api.securestorage.SecureStorageService import org.matrix.android.sdk.api.settings.LightweightSettingsStorage import org.matrix.android.sdk.internal.SessionManager import org.matrix.android.sdk.internal.auth.AuthModule import org.matrix.android.sdk.internal.auth.SessionParamsStore +import org.matrix.android.sdk.internal.debug.DebugModule import org.matrix.android.sdk.internal.raw.RawModule import org.matrix.android.sdk.internal.session.MockHttpInterceptor import org.matrix.android.sdk.internal.session.TestInterceptor @@ -49,9 +52,11 @@ import java.io.File NetworkModule::class, AuthModule::class, RawModule::class, + DebugModule::class, SettingsModule::class, SystemModule::class, - NoOpTestModule::class + NoOpTestModule::class, + SecureStorageModule::class, ] ) @MatrixScope @@ -94,13 +99,17 @@ internal interface MatrixComponent { fun sessionManager(): SessionManager + fun secureStorageService(): SecureStorageService + fun matrixWorkerFactory(): MatrixWorkerFactory fun inject(matrix: Matrix) @Component.Factory interface Factory { - fun create(@BindsInstance context: Context, - @BindsInstance matrixConfiguration: MatrixConfiguration): MatrixComponent + fun create( + @BindsInstance context: Context, + @BindsInstance matrixConfiguration: MatrixConfiguration + ): MatrixComponent } } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/di/NetworkModule.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/di/NetworkModule.kt index 0cbbe1210d..b5b46a3f5a 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/di/NetworkModule.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/di/NetworkModule.kt @@ -29,6 +29,7 @@ import org.matrix.android.sdk.api.MatrixConfiguration import org.matrix.android.sdk.internal.network.ApiInterceptor import org.matrix.android.sdk.internal.network.TimeOutInterceptor import org.matrix.android.sdk.internal.network.UserAgentInterceptor +import org.matrix.android.sdk.internal.network.httpclient.applyMatrixConfiguration import org.matrix.android.sdk.internal.network.interceptors.CurlLoggingInterceptor import org.matrix.android.sdk.internal.network.interceptors.FormattedJsonHttpLogger import java.util.Collections @@ -62,13 +63,15 @@ internal object NetworkModule { @Provides @JvmStatic @Unauthenticated - fun providesOkHttpClient(matrixConfiguration: MatrixConfiguration, - stethoInterceptor: StethoInterceptor, - timeoutInterceptor: TimeOutInterceptor, - userAgentInterceptor: UserAgentInterceptor, - httpLoggingInterceptor: HttpLoggingInterceptor, - curlLoggingInterceptor: CurlLoggingInterceptor, - apiInterceptor: ApiInterceptor): OkHttpClient { + fun providesOkHttpClient( + matrixConfiguration: MatrixConfiguration, + stethoInterceptor: StethoInterceptor, + timeoutInterceptor: TimeOutInterceptor, + userAgentInterceptor: UserAgentInterceptor, + httpLoggingInterceptor: HttpLoggingInterceptor, + curlLoggingInterceptor: CurlLoggingInterceptor, + apiInterceptor: ApiInterceptor + ): OkHttpClient { val spec = ConnectionSpec.Builder(matrixConfiguration.connectionSpec).build() return OkHttpClient.Builder() @@ -90,11 +93,9 @@ internal object NetworkModule { if (BuildConfig.LOG_PRIVATE_DATA) { addInterceptor(curlLoggingInterceptor) } - matrixConfiguration.proxy?.let { - proxy(it) - } } .connectionSpecs(Collections.singletonList(spec)) + .applyMatrixConfiguration(matrixConfiguration) .build() } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/di/WorkManagerProvider.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/di/WorkManagerProvider.kt index c59936af94..ad28286a84 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/di/WorkManagerProvider.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/di/WorkManagerProvider.kt @@ -59,8 +59,10 @@ internal class WorkManagerProvider @Inject constructor( /** * Create a PeriodicWorkRequestBuilder, with the Matrix SDK tag. */ - inline fun matrixPeriodicWorkRequestBuilder(repeatInterval: Long, - repeatIntervalTimeUnit: TimeUnit) = + inline fun matrixPeriodicWorkRequestBuilder( + repeatInterval: Long, + repeatIntervalTimeUnit: TimeUnit + ) = PeriodicWorkRequestBuilder(repeatInterval, repeatIntervalTimeUnit) .addTag(tag) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/federation/FederationModule.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/federation/FederationModule.kt index a4eef80c58..bbb29b6255 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/federation/FederationModule.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/federation/FederationModule.kt @@ -34,9 +34,11 @@ internal abstract class FederationModule { companion object { @Provides @JvmStatic - fun providesFederationAPI(@Unauthenticated okHttpClient: Lazy, - sessionParams: SessionParams, - retrofitFactory: RetrofitFactory): FederationAPI { + fun providesFederationAPI( + @Unauthenticated okHttpClient: Lazy, + sessionParams: SessionParams, + retrofitFactory: RetrofitFactory + ): FederationAPI { return retrofitFactory.create(okHttpClient, sessionParams.homeServerUrlBase) .create(FederationAPI::class.java) } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/legacy/DefaultLegacySessionImporter.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/legacy/DefaultLegacySessionImporter.kt index 0a76fb2eef..56d9cc2143 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/legacy/DefaultLegacySessionImporter.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/legacy/DefaultLegacySessionImporter.kt @@ -132,7 +132,7 @@ internal class DefaultLegacySessionImporter @Inject constructor( bytes = it.bytes, hashType = when (it.type) { LegacyFingerprint.HashType.SHA1, - null -> Fingerprint.HashType.SHA1 + null -> Fingerprint.HashType.SHA1 LegacyFingerprint.HashType.SHA256 -> Fingerprint.HashType.SHA256 } ) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/network/NetworkCallbackStrategy.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/network/NetworkCallbackStrategy.kt index 8e18d2d850..f75fb01746 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/network/NetworkCallbackStrategy.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/network/NetworkCallbackStrategy.kt @@ -31,8 +31,10 @@ internal interface NetworkCallbackStrategy { fun unregister() } -internal class FallbackNetworkCallbackStrategy @Inject constructor(private val context: Context, - private val networkInfoReceiver: NetworkInfoReceiver) : NetworkCallbackStrategy { +internal class FallbackNetworkCallbackStrategy @Inject constructor( + private val context: Context, + private val networkInfoReceiver: NetworkInfoReceiver +) : NetworkCallbackStrategy { @Suppress("DEPRECATION") val filter = IntentFilter(ConnectivityManager.CONNECTIVITY_ACTION) 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 65bf3fcadf..c54227a786 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 @@ -41,9 +41,11 @@ internal interface NetworkConnectivityChecker { } @SessionScope -internal class DefaultNetworkConnectivityChecker @Inject constructor(private val homeServerPinger: HomeServerPinger, - private val backgroundDetectionObserver: BackgroundDetectionObserver, - private val networkCallbackStrategy: NetworkCallbackStrategy) : +internal class DefaultNetworkConnectivityChecker @Inject constructor( + private val homeServerPinger: HomeServerPinger, + private val backgroundDetectionObserver: BackgroundDetectionObserver, + private val networkCallbackStrategy: NetworkCallbackStrategy +) : NetworkConnectivityChecker { private val hasInternetAccess = AtomicBoolean(true) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/network/ProgressRequestBody.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/network/ProgressRequestBody.kt index 818d7d6af3..6e4d064a0f 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/network/ProgressRequestBody.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/network/ProgressRequestBody.kt @@ -26,8 +26,10 @@ import okio.buffer import org.matrix.android.sdk.api.extensions.tryOrNull import java.io.IOException -internal class ProgressRequestBody(private val delegate: RequestBody, - private val listener: Listener) : RequestBody() { +internal class ProgressRequestBody( + private val delegate: RequestBody, + private val listener: Listener +) : RequestBody() { private lateinit var countingSink: CountingSink diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/network/Request.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/network/Request.kt index 87a98e03f6..fefb7fb5e3 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/network/Request.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/network/Request.kt @@ -40,11 +40,13 @@ import java.io.IOException * @param maxRetriesCount the max number of retries * @param requestBlock a suspend lambda to perform the network request */ -internal suspend inline fun executeRequest(globalErrorReceiver: GlobalErrorReceiver?, - canRetry: Boolean = false, - maxDelayBeforeRetry: Long = 32_000L, - maxRetriesCount: Int = 4, - noinline requestBlock: suspend () -> DATA): DATA { +internal suspend inline fun executeRequest( + globalErrorReceiver: GlobalErrorReceiver?, + canRetry: Boolean = false, + maxDelayBeforeRetry: Long = 32_000L, + maxRetriesCount: Int = 4, + noinline requestBlock: suspend () -> DATA +): DATA { var currentRetryCount = 0 var currentDelay = 1_000L @@ -54,8 +56,8 @@ internal suspend inline fun executeRequest(globalErrorReceiver: GlobalErr } catch (throwable: Throwable) { val exception = when (throwable) { is KotlinNullPointerException -> IllegalStateException("The request returned a null body") - is HttpException -> throwable.toFailure(globalErrorReceiver) - else -> throwable + is HttpException -> throwable.toFailure(globalErrorReceiver) + else -> throwable } // Log some details about the request which has failed. @@ -92,11 +94,11 @@ internal suspend inline fun executeRequest(globalErrorReceiver: GlobalErr // Try again (loop) } else { throw when (exception) { - is IOException -> Failure.NetworkConnection(exception) + is IOException -> Failure.NetworkConnection(exception) is Failure.ServerError, is Failure.OtherServerError, is CancellationException -> exception - else -> Failure.Unknown(exception) + else -> Failure.Unknown(exception) } } } 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 71df7c08be..2e8b2bc771 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 @@ -19,19 +19,23 @@ package org.matrix.android.sdk.internal.network import org.matrix.android.sdk.internal.network.executeRequest as internalExecuteRequest internal interface RequestExecutor { - suspend fun executeRequest(globalErrorReceiver: GlobalErrorReceiver?, - canRetry: Boolean = false, - maxDelayBeforeRetry: Long = 32_000L, - maxRetriesCount: Int = 4, - requestBlock: suspend () -> DATA): DATA + suspend fun executeRequest( + globalErrorReceiver: GlobalErrorReceiver?, + canRetry: Boolean = false, + maxDelayBeforeRetry: Long = 32_000L, + maxRetriesCount: Int = 4, + requestBlock: suspend () -> DATA + ): DATA } internal object DefaultRequestExecutor : RequestExecutor { - override suspend fun executeRequest(globalErrorReceiver: GlobalErrorReceiver?, - canRetry: Boolean, - maxDelayBeforeRetry: Long, - maxRetriesCount: Int, - requestBlock: suspend () -> DATA): DATA { + override suspend fun executeRequest( + globalErrorReceiver: GlobalErrorReceiver?, + canRetry: Boolean, + maxDelayBeforeRetry: Long, + maxRetriesCount: Int, + requestBlock: suspend () -> DATA + ): DATA { return internalExecuteRequest(globalErrorReceiver, canRetry, maxDelayBeforeRetry, maxRetriesCount, requestBlock) } } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/network/RetrofitExtensions.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/network/RetrofitExtensions.kt index 5268ea851d..30f7cdd65b 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/network/RetrofitExtensions.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/network/RetrofitExtensions.kt @@ -92,10 +92,10 @@ private fun toFailure(errorBody: ResponseBody?, httpCode: Int, globalErrorReceiv globalErrorReceiver?.handleGlobalError(GlobalError.ConsentNotGivenError(matrixError.consentUri)) } httpCode == HttpURLConnection.HTTP_UNAUTHORIZED && /* 401 */ - matrixError.code == MatrixError.M_UNKNOWN_TOKEN -> { + matrixError.code == MatrixError.M_UNKNOWN_TOKEN -> { globalErrorReceiver?.handleGlobalError(GlobalError.InvalidToken(matrixError.isSoftLogout.orFalse())) } - matrixError.code == MatrixError.ORG_MATRIX_EXPIRED_ACCOUNT -> { + matrixError.code == MatrixError.ORG_MATRIX_EXPIRED_ACCOUNT -> { globalErrorReceiver?.handleGlobalError(GlobalError.ExpiredAccount) } } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/network/UnitConverterFactory.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/network/UnitConverterFactory.kt index f2571ab73f..8dcff59cba 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/network/UnitConverterFactory.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/network/UnitConverterFactory.kt @@ -22,8 +22,10 @@ import retrofit2.Retrofit import java.lang.reflect.Type internal object UnitConverterFactory : Converter.Factory() { - override fun responseBodyConverter(type: Type, annotations: Array, - retrofit: Retrofit): Converter? { + override fun responseBodyConverter( + type: Type, annotations: Array, + retrofit: Retrofit + ): Converter? { return if (type == Unit::class.java) UnitConverter else null } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/network/UserAgentHolder.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/network/UserAgentHolder.kt index 00e15c283e..28d96dfce7 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/network/UserAgentHolder.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/network/UserAgentHolder.kt @@ -24,8 +24,10 @@ import timber.log.Timber import javax.inject.Inject @MatrixScope -internal class UserAgentHolder @Inject constructor(private val context: Context, - matrixConfiguration: MatrixConfiguration) { +internal class UserAgentHolder @Inject constructor( + private val context: Context, + matrixConfiguration: MatrixConfiguration +) { var userAgent: String = "" private set diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/network/httpclient/OkHttpClientUtil.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/network/httpclient/OkHttpClientUtil.kt index 3920c3b527..1c395c2d61 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/network/httpclient/OkHttpClientUtil.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/network/httpclient/OkHttpClientUtil.kt @@ -17,6 +17,7 @@ package org.matrix.android.sdk.internal.network.httpclient import okhttp3.OkHttpClient +import org.matrix.android.sdk.api.MatrixConfiguration import org.matrix.android.sdk.api.auth.data.HomeServerConnectionConfig import org.matrix.android.sdk.internal.network.AccessTokenInterceptor import org.matrix.android.sdk.internal.network.interceptors.CurlLoggingInterceptor @@ -51,3 +52,17 @@ internal fun OkHttpClient.Builder.addSocketFactory(homeServerConnectionConfig: H return this } + +internal fun OkHttpClient.Builder.applyMatrixConfiguration(matrixConfiguration: MatrixConfiguration): OkHttpClient.Builder { + matrixConfiguration.proxy?.let { + proxy(it) + } + + // Move networkInterceptors provided in the configuration after all the others + interceptors().removeAll(matrixConfiguration.networkInterceptors) + matrixConfiguration.networkInterceptors.forEach { + addInterceptor(it) + } + + return this +} diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/network/parsing/ForceToBoolean.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/network/parsing/ForceToBoolean.kt index 628486bb5f..b4925429d0 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/network/parsing/ForceToBoolean.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/network/parsing/ForceToBoolean.kt @@ -37,9 +37,9 @@ internal class ForceToBooleanJsonAdapter { @ForceToBoolean fun fromJson(reader: JsonReader): Boolean { return when (val token = reader.peek()) { - JsonReader.Token.NUMBER -> reader.nextInt() != 0 + JsonReader.Token.NUMBER -> reader.nextInt() != 0 JsonReader.Token.BOOLEAN -> reader.nextBoolean() - else -> { + else -> { Timber.e("Expecting a boolean or a int but get: $token") reader.skipValue() false diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/network/parsing/RuntimeJsonAdapterFactory.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/network/parsing/RuntimeJsonAdapterFactory.kt index dd41b9f6fc..7cf3603cc5 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/network/parsing/RuntimeJsonAdapterFactory.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/network/parsing/RuntimeJsonAdapterFactory.kt @@ -70,11 +70,13 @@ internal class RuntimeJsonAdapterFactory( } @Suppress("UNCHECKED_CAST") - internal class RuntimeJsonAdapter(val labelKey: String, - val labelToAdapter: Map>, - val typeToLabel: Map, - val objectJsonAdapter: JsonAdapter, - val fallbackAdapter: JsonAdapter) : JsonAdapter() { + internal class RuntimeJsonAdapter( + val labelKey: String, + val labelToAdapter: Map>, + val typeToLabel: Map, + val objectJsonAdapter: JsonAdapter, + val fallbackAdapter: JsonAdapter + ) : JsonAdapter() { @Throws(IOException::class) override fun fromJson(reader: JsonReader): Any? { val peekedToken = reader.peek() diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/network/ssl/PinnedTrustManager.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/network/ssl/PinnedTrustManager.kt index 539570cdd9..1b957c3855 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/network/ssl/PinnedTrustManager.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/network/ssl/PinnedTrustManager.kt @@ -29,8 +29,10 @@ import javax.net.ssl.X509TrustManager * @property defaultTrustManager Optional trust manager to fall back on if cert does not match * any of the fingerprints. Can be null. */ -internal class PinnedTrustManager(private val fingerprints: List, - private val defaultTrustManager: X509TrustManager?) : X509TrustManager { +internal class PinnedTrustManager( + private val fingerprints: List, + private val defaultTrustManager: X509TrustManager? +) : X509TrustManager { @Throws(CertificateException::class) override fun checkClientTrusted(chain: Array, s: String) { diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/network/ssl/PinnedTrustManagerApi24.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/network/ssl/PinnedTrustManagerApi24.kt index 191bb90a67..ef703f3b61 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/network/ssl/PinnedTrustManagerApi24.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/network/ssl/PinnedTrustManagerApi24.kt @@ -34,8 +34,10 @@ import javax.net.ssl.X509ExtendedTrustManager * any of the fingerprints. Can be null. */ @RequiresApi(Build.VERSION_CODES.N) -internal class PinnedTrustManagerApi24(private val fingerprints: List, - private val defaultTrustManager: X509ExtendedTrustManager?) : X509ExtendedTrustManager() { +internal class PinnedTrustManagerApi24( + private val fingerprints: List, + private val defaultTrustManager: X509ExtendedTrustManager? +) : X509ExtendedTrustManager() { @Throws(CertificateException::class) override fun checkClientTrusted(chain: Array, authType: String, engine: SSLEngine?) { diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/network/ssl/PinnedTrustManagerProvider.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/network/ssl/PinnedTrustManagerProvider.kt index f01ee7af24..bfdc4e537e 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/network/ssl/PinnedTrustManagerProvider.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/network/ssl/PinnedTrustManagerProvider.kt @@ -25,8 +25,10 @@ internal object PinnedTrustManagerProvider { // Set to false to perform some tests private const val USE_DEFAULT_TRUST_MANAGER = true - fun provide(fingerprints: List?, - defaultTrustManager: X509TrustManager?): X509TrustManager { + fun provide( + fingerprints: List?, + defaultTrustManager: X509TrustManager? + ): X509TrustManager { return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N && defaultTrustManager is X509ExtendedTrustManager) { PinnedTrustManagerApi24( fingerprints.orEmpty(), diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/query/QueryRoomOrderProcessor.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/query/QueryRoomOrderProcessor.kt index c5ea2d48ad..8df0482993 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/query/QueryRoomOrderProcessor.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/query/QueryRoomOrderProcessor.kt @@ -24,10 +24,10 @@ import org.matrix.android.sdk.internal.database.model.RoomSummaryEntityFields internal fun RealmQuery.process(sortOrder: RoomSortOrder): RealmQuery { when (sortOrder) { - RoomSortOrder.NAME -> { + RoomSortOrder.NAME -> { sort(RoomSummaryEntityFields.DISPLAY_NAME, Sort.ASCENDING) } - RoomSortOrder.ACTIVITY -> { + RoomSortOrder.ACTIVITY -> { sort(RoomSummaryEntityFields.LAST_ACTIVITY_TIME, Sort.DESCENDING) } RoomSortOrder.PRIORITY_AND_ACTIVITY -> { @@ -40,7 +40,7 @@ internal fun RealmQuery.process(sortOrder: RoomSortOrder): Re arrayOf(Sort.DESCENDING, Sort.ASCENDING, Sort.DESCENDING) ) } - RoomSortOrder.NONE -> { + RoomSortOrder.NONE -> { } } return this diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/query/QueryStringValueProcessor.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/query/QueryStringValueProcessor.kt index ba4d05e747..b2ab9879df 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/query/QueryStringValueProcessor.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/query/QueryStringValueProcessor.kt @@ -31,12 +31,12 @@ internal class QueryStringValueProcessor @Inject constructor( fun RealmQuery.process(field: String, queryStringValue: QueryStringValue): RealmQuery { return when (queryStringValue) { is QueryStringValue.NoCondition -> this - is QueryStringValue.IsNotNull -> isNotNull(field) - is QueryStringValue.IsNull -> isNull(field) - is QueryStringValue.IsEmpty -> isEmpty(field) - is QueryStringValue.IsNotEmpty -> isNotEmpty(field) - is ContentQueryStringValue -> when (queryStringValue) { - is QueryStringValue.Equals -> equalTo(field, queryStringValue.toRealmValue(), queryStringValue.case.toRealmCase()) + is QueryStringValue.IsNotNull -> isNotNull(field) + is QueryStringValue.IsNull -> isNull(field) + is QueryStringValue.IsEmpty -> isEmpty(field) + is QueryStringValue.IsNotEmpty -> isNotEmpty(field) + is ContentQueryStringValue -> when (queryStringValue) { + is QueryStringValue.Equals -> equalTo(field, queryStringValue.toRealmValue(), queryStringValue.case.toRealmCase()) is QueryStringValue.Contains -> contains(field, queryStringValue.toRealmValue(), queryStringValue.case.toRealmCase()) } } @@ -44,7 +44,7 @@ internal class QueryStringValueProcessor @Inject constructor( private fun ContentQueryStringValue.toRealmValue(): String { return when (case) { - QueryStringValue.Case.NORMALIZED -> normalizer.normalize(string) + QueryStringValue.Case.NORMALIZED -> normalizer.normalize(string) QueryStringValue.Case.SENSITIVE, QueryStringValue.Case.INSENSITIVE -> string } @@ -55,6 +55,6 @@ private fun QueryStringValue.Case.toRealmCase(): Case { return when (this) { QueryStringValue.Case.INSENSITIVE -> Case.INSENSITIVE QueryStringValue.Case.SENSITIVE, - QueryStringValue.Case.NORMALIZED -> Case.SENSITIVE + QueryStringValue.Case.NORMALIZED -> Case.SENSITIVE } } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/raw/GetUrlTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/raw/GetUrlTask.kt index d0e2534e7a..87adad9063 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/raw/GetUrlTask.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/raw/GetUrlTask.kt @@ -42,8 +42,8 @@ internal class DefaultGetUrlTask @Inject constructor( override suspend fun execute(params: GetUrlTask.Params): String { return when (params.cacheStrategy) { - CacheStrategy.NoCache -> doRequest(params.url) - is CacheStrategy.TtlCache -> doRequestWithCache( + CacheStrategy.NoCache -> doRequest(params.url) + is CacheStrategy.TtlCache -> doRequestWithCache( params.url, params.cacheStrategy.validityDurationInMillis, params.cacheStrategy.strict diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/raw/RawModule.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/raw/RawModule.kt index a830976671..4b643f1fef 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/raw/RawModule.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/raw/RawModule.kt @@ -51,8 +51,10 @@ internal abstract class RawModule { @Provides @GlobalDatabase @MatrixScope - fun providesRealmConfiguration(realmKeysUtils: RealmKeysUtils, - globalRealmMigration: GlobalRealmMigration): RealmConfiguration { + fun providesRealmConfiguration( + realmKeysUtils: RealmKeysUtils, + globalRealmMigration: GlobalRealmMigration + ): RealmConfiguration { return RealmConfiguration.Builder() .apply { realmKeysUtils.configureEncryption(this, DB_ALIAS) @@ -67,8 +69,10 @@ internal abstract class RawModule { @Provides @JvmStatic - fun providesRawAPI(@Unauthenticated okHttpClient: Lazy, - retrofitFactory: RetrofitFactory): RawAPI { + fun providesRawAPI( + @Unauthenticated okHttpClient: Lazy, + retrofitFactory: RetrofitFactory + ): RawAPI { return retrofitFactory.create(okHttpClient, "https://example.org").create(RawAPI::class.java) } } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/securestorage/DefaultSecureStorageService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/securestorage/DefaultSecureStorageService.kt similarity index 86% rename from matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/securestorage/DefaultSecureStorageService.kt rename to matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/securestorage/DefaultSecureStorageService.kt index ef8133dd15..8f6605d657 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/securestorage/DefaultSecureStorageService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/securestorage/DefaultSecureStorageService.kt @@ -14,9 +14,10 @@ * limitations under the License. */ -package org.matrix.android.sdk.internal.session.securestorage +package org.matrix.android.sdk.internal.securestorage -import org.matrix.android.sdk.api.session.securestorage.SecureStorageService +import org.matrix.android.sdk.api.securestorage.SecretStoringUtils +import org.matrix.android.sdk.api.securestorage.SecureStorageService import java.io.InputStream import java.io.OutputStream import javax.inject.Inject 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 760aa41081..bbf260a3f1 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 @@ -80,10 +80,12 @@ internal class DefaultFileService @Inject constructor( * Download file in the cache folder, and eventually decrypt it. * TODO looks like files are copied 3 times */ - override suspend fun downloadFile(fileName: String, - mimeType: String?, - url: String?, - elementToDecrypt: ElementToDecrypt?): File { + override suspend fun downloadFile( + fileName: String, + mimeType: String?, + url: String?, + elementToDecrypt: ElementToDecrypt? + ): File { url ?: throw IllegalArgumentException("url is null") Timber.v("## FileService downloadFile $url") @@ -125,7 +127,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) @@ -218,11 +220,13 @@ internal class DefaultFileService @Inject constructor( return result.getOrThrow() } - fun storeDataFor(mxcUrl: String, - filename: String?, - mimeType: String?, - originalFile: File, - encryptedFile: File?) { + fun storeDataFor( + mxcUrl: String, + filename: String?, + mimeType: String?, + originalFile: File, + encryptedFile: File? + ) { val files = getFiles(mxcUrl, filename, mimeType, encryptedFile != null) if (encryptedFile != null) { // We switch the two files here, original file it the decrypted file @@ -256,10 +260,12 @@ internal class DefaultFileService @Inject constructor( } } - override fun isFileInCache(mxcUrl: String?, - fileName: String, - mimeType: String?, - elementToDecrypt: ElementToDecrypt?): Boolean { + override fun isFileInCache( + mxcUrl: String?, + fileName: String, + mimeType: String?, + elementToDecrypt: ElementToDecrypt? + ): Boolean { return fileState(mxcUrl, fileName, mimeType, elementToDecrypt) is FileService.FileState.InCache } @@ -272,10 +278,12 @@ internal class DefaultFileService @Inject constructor( fun getClearFile(): File = decryptedFile ?: file } - private fun getFiles(mxcUrl: String, - fileName: String?, - mimeType: String?, - isEncrypted: Boolean): CachedFiles { + private fun getFiles( + mxcUrl: String, + fileName: String?, + mimeType: String?, + isEncrypted: Boolean + ): CachedFiles { val hashFolder = mxcUrl.md5() val safeFileName = safeFileName(fileName, mimeType) return if (isEncrypted) { @@ -293,10 +301,12 @@ internal class DefaultFileService @Inject constructor( } } - override fun fileState(mxcUrl: String?, - fileName: String, - mimeType: String?, - elementToDecrypt: ElementToDecrypt?): FileService.FileState { + override fun fileState( + mxcUrl: String?, + fileName: String, + mimeType: String?, + elementToDecrypt: ElementToDecrypt? + ): FileService.FileState { mxcUrl ?: return FileService.FileState.Unknown val files = getFiles(mxcUrl, fileName, mimeType, elementToDecrypt != null) if (files.file.exists()) { @@ -314,10 +324,12 @@ internal class DefaultFileService @Inject constructor( * Use this URI and pass it to intent using flag Intent.FLAG_GRANT_READ_URI_PERMISSION * (if not other app won't be able to access it). */ - override fun getTemporarySharableURI(mxcUrl: String?, - fileName: String, - mimeType: String?, - elementToDecrypt: ElementToDecrypt?): Uri? { + override fun getTemporarySharableURI( + mxcUrl: String?, + fileName: String, + mimeType: String?, + elementToDecrypt: ElementToDecrypt? + ): Uri? { mxcUrl ?: return null // this string could be extracted no? val authority = "${context.packageName}.mx-sdk.fileprovider" 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 5f77cfb23a..7c50a0ff84 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 @@ -44,7 +44,6 @@ import org.matrix.android.sdk.api.session.file.FileService import org.matrix.android.sdk.api.session.group.GroupService import org.matrix.android.sdk.api.session.homeserver.HomeServerCapabilitiesService import org.matrix.android.sdk.api.session.identity.IdentityService -import org.matrix.android.sdk.api.session.initsync.SyncStatusService import org.matrix.android.sdk.api.session.integrationmanager.IntegrationManagerService import org.matrix.android.sdk.api.session.media.MediaService import org.matrix.android.sdk.api.session.openid.OpenIdService @@ -56,11 +55,11 @@ 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 -import org.matrix.android.sdk.api.session.securestorage.SecureStorageService import org.matrix.android.sdk.api.session.securestorage.SharedSecretStorageService import org.matrix.android.sdk.api.session.signout.SignOutService import org.matrix.android.sdk.api.session.space.SpaceService import org.matrix.android.sdk.api.session.sync.FilterService +import org.matrix.android.sdk.api.session.sync.SyncService import org.matrix.android.sdk.api.session.terms.TermsService import org.matrix.android.sdk.api.session.thirdparty.ThirdPartyService import org.matrix.android.sdk.api.session.typing.TypingUsersTracker @@ -71,18 +70,16 @@ import org.matrix.android.sdk.internal.auth.SSO_UIA_FALLBACK_PATH import org.matrix.android.sdk.internal.auth.SessionParamsStore import org.matrix.android.sdk.internal.crypto.DefaultCryptoService import org.matrix.android.sdk.internal.database.tools.RealmDebugTools +import org.matrix.android.sdk.internal.di.ContentScannerDatabase +import org.matrix.android.sdk.internal.di.CryptoDatabase +import org.matrix.android.sdk.internal.di.IdentityDatabase import org.matrix.android.sdk.internal.di.SessionDatabase import org.matrix.android.sdk.internal.di.SessionId import org.matrix.android.sdk.internal.di.UnauthenticatedWithCertificate import org.matrix.android.sdk.internal.di.WorkManagerProvider import org.matrix.android.sdk.internal.network.GlobalErrorHandler -import org.matrix.android.sdk.internal.session.sync.SyncTokenStore -import org.matrix.android.sdk.internal.session.sync.job.SyncThread -import org.matrix.android.sdk.internal.session.sync.job.SyncWorker import org.matrix.android.sdk.internal.util.createUIHandler -import timber.log.Timber import javax.inject.Inject -import javax.inject.Provider @SessionScope internal class DefaultSession @Inject constructor( @@ -93,6 +90,9 @@ internal class DefaultSession @Inject constructor( override val sessionId: String, override val coroutineDispatchers: MatrixCoroutineDispatchers, @SessionDatabase private val realmConfiguration: RealmConfiguration, + @CryptoDatabase private val realmConfigurationCrypto: RealmConfiguration, + @IdentityDatabase private val realmConfigurationIdentity: RealmConfiguration, + @ContentScannerDatabase private val realmConfigurationContentScanner: RealmConfiguration, private val lifecycleObservers: Set<@JvmSuppressWildcards SessionLifecycleObserver>, private val sessionListeners: SessionListeners, private val roomService: Lazy, @@ -110,18 +110,15 @@ internal class DefaultSession @Inject constructor( private val cryptoService: Lazy, private val defaultFileService: Lazy, private val permalinkService: Lazy, - private val secureStorageService: Lazy, private val profileService: Lazy, + private val syncService: Lazy, private val mediaService: Lazy, private val widgetService: Lazy, - private val syncThreadProvider: Provider, private val contentUrlResolver: ContentUrlResolver, - private val syncTokenStore: SyncTokenStore, private val sessionParamsStore: SessionParamsStore, private val contentUploadProgressTracker: ContentUploadStateTracker, private val typingUsersTracker: TypingUsersTracker, private val contentDownloadStateTracker: ContentDownloadStateTracker, - private val syncStatusService: Lazy, private val homeServerCapabilitiesService: Lazy, private val accountDataService: Lazy, private val sharedSecretStorageService: Lazy, @@ -138,14 +135,11 @@ internal class DefaultSession @Inject constructor( private val toDeviceService: Lazy, private val eventStreamService: Lazy, @UnauthenticatedWithCertificate - private val unauthenticatedWithCertificateOkHttpClient: Lazy + private val unauthenticatedWithCertificateOkHttpClient: Lazy, + private val sessionState: SessionState, ) : Session, GlobalErrorHandler.Listener { - private var isOpen = false - - private var syncThread: SyncThread? = null - private val uiHandler = createUIHandler() override val isOpenable: Boolean @@ -153,8 +147,7 @@ internal class DefaultSession @Inject constructor( @MainThread override fun open() { - assert(!isOpen) - isOpen = true + sessionState.setIsOpen(true) globalErrorHandler.listener = this cryptoService.get().ensureDevice() uiHandler.post { @@ -167,40 +160,9 @@ internal class DefaultSession @Inject constructor( } } - override fun requireBackgroundSync() { - SyncWorker.requireBackgroundSync(workManagerProvider, sessionId) - } - - override fun startAutomaticBackgroundSync(timeOutInSeconds: Long, repeatDelayInSeconds: Long) { - SyncWorker.automaticallyBackgroundSync(workManagerProvider, sessionId, timeOutInSeconds, repeatDelayInSeconds) - } - - override fun stopAnyBackgroundSync() { - SyncWorker.stopAnyBackgroundSync(workManagerProvider) - } - - override fun startSync(fromForeground: Boolean) { - Timber.i("Starting sync thread") - assert(isOpen) - val localSyncThread = getSyncThread() - localSyncThread.setInitialForeground(fromForeground) - if (!localSyncThread.isAlive) { - localSyncThread.start() - } else { - localSyncThread.restart() - Timber.w("Attempt to start an already started thread") - } - } - - override fun stopSync() { - assert(isOpen) - syncThread?.kill() - syncThread = null - } - override fun close() { - assert(isOpen) - stopSync() + assert(sessionState.isOpen) + syncService.get().stopSync() // timelineEventDecryptor.destroy() uiHandler.post { lifecycleObservers.forEach { it.onSessionStopped(this) } @@ -210,28 +172,12 @@ internal class DefaultSession @Inject constructor( } cryptoService.get().close() globalErrorHandler.listener = null - isOpen = false - } - - override fun getSyncStateLive() = getSyncThread().liveState() - - override fun syncFlow() = getSyncThread().syncFlow() - - override fun getSyncState() = getSyncThread().currentState() - - override fun hasAlreadySynced(): Boolean { - return syncTokenStore.getLastToken() != null - } - - private fun getSyncThread(): SyncThread { - return syncThread ?: syncThreadProvider.get().also { - syncThread = it - } + sessionState.setIsOpen(false) } override suspend fun clearCache() { - stopSync() - stopAnyBackgroundSync() + syncService.get().stopSync() + syncService.get().stopAnyBackgroundSync() uiHandler.post { lifecycleObservers.forEach { it.onClearCache(this) @@ -271,8 +217,7 @@ internal class DefaultSession @Inject constructor( 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 syncService(): SyncService = syncService.get() override fun profileService(): ProfileService = profileService.get() override fun presenceService(): PresenceService = presenceService.get() override fun accountService(): AccountService = accountService.get() @@ -323,5 +268,17 @@ internal class DefaultSession @Inject constructor( override fun logDbUsageInfo() { RealmDebugTools(realmConfiguration).logInfo("Session") + RealmDebugTools(realmConfigurationCrypto).logInfo("Crypto") + RealmDebugTools(realmConfigurationIdentity).logInfo("Identity") + RealmDebugTools(realmConfigurationContentScanner).logInfo("ContentScanner") + } + + override fun getRealmConfigurations(): List { + return listOf( + realmConfiguration, + realmConfigurationCrypto, + realmConfigurationIdentity, + realmConfigurationContentScanner, + ) } } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/SessionComponent.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/SessionComponent.kt index 050480e6c9..d3cae3ac2d 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/SessionComponent.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/SessionComponent.kt @@ -20,6 +20,7 @@ import dagger.BindsInstance import dagger.Component import org.matrix.android.sdk.api.MatrixCoroutineDispatchers import org.matrix.android.sdk.api.auth.data.SessionParams +import org.matrix.android.sdk.api.securestorage.SecureStorageModule import org.matrix.android.sdk.api.session.Session import org.matrix.android.sdk.internal.crypto.CryptoModule import org.matrix.android.sdk.internal.crypto.crosssigning.UpdateTrustWorker @@ -46,6 +47,7 @@ import org.matrix.android.sdk.internal.session.profile.ProfileModule import org.matrix.android.sdk.internal.session.pushers.AddPusherWorker import org.matrix.android.sdk.internal.session.pushers.PushersModule import org.matrix.android.sdk.internal.session.room.RoomModule +import org.matrix.android.sdk.internal.session.room.aggregation.livelocation.DeactivateLiveLocationShareWorker import org.matrix.android.sdk.internal.session.room.send.MultipleEventSendingDispatcherWorker import org.matrix.android.sdk.internal.session.room.send.RedactEventWorker import org.matrix.android.sdk.internal.session.room.send.SendEventWorker @@ -97,7 +99,8 @@ import org.matrix.android.sdk.internal.util.system.SystemModule ThirdPartyModule::class, SpaceModule::class, PresenceModule::class, - RequestModule::class + RequestModule::class, + SecureStorageModule::class, ] ) @SessionScope @@ -131,10 +134,13 @@ internal interface SessionComponent { fun inject(worker: UpdateTrustWorker) + fun inject(worker: DeactivateLiveLocationShareWorker) + @Component.Factory interface Factory { fun create( matrixComponent: MatrixComponent, - @BindsInstance sessionParams: SessionParams): SessionComponent + @BindsInstance sessionParams: SessionParams + ): SessionComponent } } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/SessionListeners.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/SessionListeners.kt index 756b9cef83..0dae24e64b 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/SessionListeners.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/SessionListeners.kt @@ -19,12 +19,13 @@ package org.matrix.android.sdk.internal.session import org.matrix.android.sdk.api.extensions.tryOrNull import org.matrix.android.sdk.api.session.Session import timber.log.Timber +import java.util.concurrent.CopyOnWriteArraySet import javax.inject.Inject @SessionScope internal class SessionListeners @Inject constructor() { - private val listeners = mutableSetOf() + private val listeners = CopyOnWriteArraySet() fun addListener(listener: Session.Listener) { synchronized(listeners) { diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/SessionModule.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/SessionModule.kt index 9208ff219b..f8a52f0b7e 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/SessionModule.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/SessionModule.kt @@ -39,10 +39,8 @@ import org.matrix.android.sdk.api.session.ToDeviceService import org.matrix.android.sdk.api.session.accountdata.SessionAccountDataService import org.matrix.android.sdk.api.session.events.EventService import org.matrix.android.sdk.api.session.homeserver.HomeServerCapabilitiesService -import org.matrix.android.sdk.api.session.initsync.SyncStatusService import org.matrix.android.sdk.api.session.openid.OpenIdService import org.matrix.android.sdk.api.session.permalinks.PermalinkService -import org.matrix.android.sdk.api.session.securestorage.SecureStorageService import org.matrix.android.sdk.api.session.securestorage.SharedSecretStorageService import org.matrix.android.sdk.api.session.typing.TypingUsersTracker import org.matrix.android.sdk.api.util.md5 @@ -74,6 +72,7 @@ import org.matrix.android.sdk.internal.network.PreferredNetworkCallbackStrategy import org.matrix.android.sdk.internal.network.RetrofitFactory import org.matrix.android.sdk.internal.network.httpclient.addAccessTokenInterceptor import org.matrix.android.sdk.internal.network.httpclient.addSocketFactory +import org.matrix.android.sdk.internal.network.httpclient.applyMatrixConfiguration import org.matrix.android.sdk.internal.network.interceptors.CurlLoggingInterceptor import org.matrix.android.sdk.internal.network.token.AccessTokenProvider import org.matrix.android.sdk.internal.network.token.HomeserverAccessTokenProvider @@ -82,7 +81,6 @@ import org.matrix.android.sdk.internal.session.download.DownloadProgressIntercep import org.matrix.android.sdk.internal.session.events.DefaultEventService import org.matrix.android.sdk.internal.session.homeserver.DefaultHomeServerCapabilitiesService import org.matrix.android.sdk.internal.session.identity.DefaultIdentityService -import org.matrix.android.sdk.internal.session.initsync.DefaultSyncStatusService import org.matrix.android.sdk.internal.session.integrationmanager.IntegrationManager import org.matrix.android.sdk.internal.session.openid.DefaultOpenIdService import org.matrix.android.sdk.internal.session.permalinks.DefaultPermalinkService @@ -94,7 +92,6 @@ import org.matrix.android.sdk.internal.session.room.prune.RedactionEventProcesso import org.matrix.android.sdk.internal.session.room.send.queue.EventSenderProcessor import org.matrix.android.sdk.internal.session.room.send.queue.EventSenderProcessorCoroutine import org.matrix.android.sdk.internal.session.room.tombstone.RoomTombstoneEventProcessor -import org.matrix.android.sdk.internal.session.securestorage.DefaultSecureStorageService import org.matrix.android.sdk.internal.session.typing.DefaultTypingUsersTracker import org.matrix.android.sdk.internal.session.user.accountdata.DefaultSessionAccountDataService import org.matrix.android.sdk.internal.session.widgets.DefaultWidgetURLFormatter @@ -166,9 +163,11 @@ internal abstract class SessionModule { @Provides @SessionFilesDirectory @SessionScope - fun providesFilesDir(@UserMd5 userMd5: String, - @SessionId sessionId: String, - context: Context): File { + fun providesFilesDir( + @UserMd5 userMd5: String, + @SessionId sessionId: String, + context: Context + ): File { // Temporary code for migration val old = File(context.filesDir, userMd5) if (old.exists()) { @@ -181,8 +180,10 @@ internal abstract class SessionModule { @JvmStatic @Provides @SessionDownloadsDirectory - fun providesDownloadsCacheDir(@SessionId sessionId: String, - @CacheDirectory cacheFile: File): File { + fun providesDownloadsCacheDir( + @SessionId sessionId: String, + @CacheDirectory cacheFile: File + ): File { return File(cacheFile, "downloads/$sessionId") } @@ -208,8 +209,10 @@ internal abstract class SessionModule { @Provides @SessionScope @UnauthenticatedWithCertificate - fun providesOkHttpClientWithCertificate(@Unauthenticated okHttpClient: OkHttpClient, - homeServerConnectionConfig: HomeServerConnectionConfig): OkHttpClient { + fun providesOkHttpClientWithCertificate( + @Unauthenticated okHttpClient: OkHttpClient, + homeServerConnectionConfig: HomeServerConnectionConfig, + ): OkHttpClient { return okHttpClient .newBuilder() .addSocketFactory(homeServerConnectionConfig) @@ -220,10 +223,13 @@ internal abstract class SessionModule { @Provides @SessionScope @Authenticated - fun providesOkHttpClient(@UnauthenticatedWithCertificate okHttpClient: OkHttpClient, - @Authenticated accessTokenProvider: AccessTokenProvider, - @SessionId sessionId: String, - @MockHttpInterceptor testInterceptor: TestInterceptor?): OkHttpClient { + fun providesOkHttpClient( + @UnauthenticatedWithCertificate okHttpClient: OkHttpClient, + @Authenticated accessTokenProvider: AccessTokenProvider, + @SessionId sessionId: String, + @MockHttpInterceptor testInterceptor: TestInterceptor?, + matrixConfiguration: MatrixConfiguration, + ): OkHttpClient { return okHttpClient .newBuilder() .addAccessTokenInterceptor(accessTokenProvider) @@ -233,6 +239,7 @@ internal abstract class SessionModule { addInterceptor(testInterceptor) } } + .applyMatrixConfiguration(matrixConfiguration) .build() } @@ -240,11 +247,15 @@ internal abstract class SessionModule { @Provides @SessionScope @UnauthenticatedWithCertificateWithProgress - fun providesProgressOkHttpClient(@UnauthenticatedWithCertificate okHttpClient: OkHttpClient, - downloadProgressInterceptor: DownloadProgressInterceptor): OkHttpClient { - return okHttpClient.newBuilder() + fun providesProgressOkHttpClient( + @UnauthenticatedWithCertificate okHttpClient: OkHttpClient, + downloadProgressInterceptor: DownloadProgressInterceptor, + matrixConfiguration: MatrixConfiguration, + ): OkHttpClient { + return okHttpClient + .newBuilder() .apply { - // Remove the previous CurlLoggingInterceptor, to add it after the accessTokenInterceptor + // Remove the previous CurlLoggingInterceptor, to add it after the downloadProgressInterceptor val existingCurlInterceptors = interceptors().filterIsInstance() interceptors().removeAll(existingCurlInterceptors) @@ -254,15 +265,19 @@ internal abstract class SessionModule { existingCurlInterceptors.forEach { addInterceptor(it) } - }.build() + } + .applyMatrixConfiguration(matrixConfiguration) + .build() } @JvmStatic @Provides @SessionScope - fun providesRetrofit(@Authenticated okHttpClient: Lazy, - sessionParams: SessionParams, - retrofitFactory: RetrofitFactory): Retrofit { + fun providesRetrofit( + @Authenticated okHttpClient: Lazy, + sessionParams: SessionParams, + retrofitFactory: RetrofitFactory + ): Retrofit { return retrofitFactory .create(okHttpClient, sessionParams.homeServerConnectionConfig.homeServerUriBase.toString()) } @@ -270,8 +285,9 @@ internal abstract class SessionModule { @JvmStatic @Provides @SessionScope - fun providesNetworkCallbackStrategy(fallbackNetworkCallbackStrategy: Provider, - preferredNetworkCallbackStrategy: Provider + fun providesNetworkCallbackStrategy( + fallbackNetworkCallbackStrategy: Provider, + preferredNetworkCallbackStrategy: Provider ): NetworkCallbackStrategy { return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { preferredNetworkCallbackStrategy.get() @@ -349,12 +365,6 @@ internal abstract class SessionModule { @IntoSet abstract fun bindEventSenderProcessorAsSessionLifecycleObserver(processor: EventSenderProcessorCoroutine): SessionLifecycleObserver - @Binds - abstract fun bindSyncStatusService(service: DefaultSyncStatusService): SyncStatusService - - @Binds - abstract fun bindSecureStorageService(service: DefaultSecureStorageService): SecureStorageService - @Binds abstract fun bindHomeServerCapabilitiesService(service: DefaultHomeServerCapabilitiesService): HomeServerCapabilitiesService diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/SessionState.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/SessionState.kt new file mode 100644 index 0000000000..b109a9aa60 --- /dev/null +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/SessionState.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.session + +import javax.inject.Inject + +@SessionScope +internal class SessionState @Inject constructor() { + var isOpen = false + private set + + /** + * Set the new state. Throw if already in the new state. + */ + fun setIsOpen(newState: Boolean) { + assert(newState != isOpen) + isOpen = newState + } +} diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/account/ChangePasswordParams.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/account/ChangePasswordParams.kt index 1b95820918..f6778327d9 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/account/ChangePasswordParams.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/account/ChangePasswordParams.kt @@ -29,13 +29,17 @@ internal data class ChangePasswordParams( val auth: UserPasswordAuth? = null, @Json(name = "new_password") - val newPassword: String? = null + val newPassword: String? = null, + + @Json(name = "logout_devices") + val logoutDevices: Boolean = true ) { companion object { - fun create(userId: String, oldPassword: String, newPassword: String): ChangePasswordParams { + fun create(userId: String, oldPassword: String, newPassword: String, logoutAllDevices: Boolean): ChangePasswordParams { return ChangePasswordParams( auth = UserPasswordAuth(user = userId, password = oldPassword), - newPassword = newPassword + newPassword = newPassword, + logoutDevices = logoutAllDevices ) } } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/account/ChangePasswordTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/account/ChangePasswordTask.kt index 7b21ba2e63..e767950ff7 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/account/ChangePasswordTask.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/account/ChangePasswordTask.kt @@ -26,7 +26,8 @@ import javax.inject.Inject internal interface ChangePasswordTask : Task { data class Params( val password: String, - val newPassword: String + val newPassword: String, + val logoutAllDevices: Boolean ) } @@ -37,7 +38,7 @@ internal class DefaultChangePasswordTask @Inject constructor( ) : ChangePasswordTask { override suspend fun execute(params: ChangePasswordTask.Params) { - val changePasswordParams = ChangePasswordParams.create(userId, params.password, params.newPassword) + val changePasswordParams = ChangePasswordParams.create(userId, params.password, params.newPassword, params.logoutAllDevices) try { executeRequest(globalErrorReceiver) { accountAPI.changePassword(changePasswordParams) 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 9f3f1f649e..5f4d3d5fbc 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 @@ -60,10 +60,10 @@ internal class DefaultDeactivateAccountTask @Inject constructor( execute(params.copy(userAuthParam = authUpdate)) } )) { - UiaResult.SUCCESS -> { + UiaResult.SUCCESS -> { false } - UiaResult.FAILURE -> { + UiaResult.FAILURE -> { Timber.d("## UIA: propagate failure") throw throwable } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/account/DefaultAccountService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/account/DefaultAccountService.kt index dc77d7bffb..9d03ec479b 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/account/DefaultAccountService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/account/DefaultAccountService.kt @@ -20,11 +20,13 @@ import org.matrix.android.sdk.api.auth.UserInteractiveAuthInterceptor import org.matrix.android.sdk.api.session.account.AccountService import javax.inject.Inject -internal class DefaultAccountService @Inject constructor(private val changePasswordTask: ChangePasswordTask, - private val deactivateAccountTask: DeactivateAccountTask) : AccountService { +internal class DefaultAccountService @Inject constructor( + private val changePasswordTask: ChangePasswordTask, + private val deactivateAccountTask: DeactivateAccountTask +) : AccountService { - override suspend fun changePassword(password: String, newPassword: String) { - changePasswordTask.execute(ChangePasswordTask.Params(password, newPassword)) + override suspend fun changePassword(password: String, newPassword: String, logoutAllDevices: Boolean) { + changePasswordTask.execute(ChangePasswordTask.Params(password, newPassword, logoutAllDevices)) } override suspend fun deactivateAccount(eraseAllData: Boolean, userInteractiveAuthInterceptor: UserInteractiveAuthInterceptor) { 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 c4f711a9e6..48a9dfd3da 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 @@ -63,25 +63,25 @@ internal class CallSignalingHandler @Inject constructor( fun onCallEvent(event: Event) { when (event.getClearType()) { - EventType.CALL_ANSWER -> { + EventType.CALL_ANSWER -> { handleCallAnswerEvent(event) } - EventType.CALL_INVITE -> { + EventType.CALL_INVITE -> { handleCallInviteEvent(event) } - EventType.CALL_HANGUP -> { + EventType.CALL_HANGUP -> { handleCallHangupEvent(event) } - EventType.CALL_REJECT -> { + EventType.CALL_REJECT -> { handleCallRejectEvent(event) } - EventType.CALL_CANDIDATES -> { + EventType.CALL_CANDIDATES -> { handleCallCandidatesEvent(event) } - EventType.CALL_SELECT_ANSWER -> { + EventType.CALL_SELECT_ANSWER -> { handleCallSelectAnswerEvent(event) } - EventType.CALL_NEGOTIATE -> { + EventType.CALL_NEGOTIATE -> { handleCallNegotiateEvent(event) } EventType.CALL_ASSERTED_IDENTITY, 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 9ec892b65d..5b4100f276 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 @@ -79,10 +79,12 @@ internal class MxCallFactory @Inject constructor( } } - fun updateOutgoingCallWithOpponentData(call: MxCall, - userId: String, - content: CallSignalingContent, - callCapabilities: CallCapabilities?) { + fun updateOutgoingCallWithOpponentData( + call: MxCall, + userId: String, + content: CallSignalingContent, + callCapabilities: CallCapabilities? + ) { (call as? MxCallImpl)?.updateOpponentData(userId, content, callCapabilities) } } 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 796e83311f..13a77e55e1 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 @@ -220,10 +220,12 @@ internal class MxCallImpl( .also { eventSenderProcessor.postEvent(it) } } - override suspend fun transfer(targetUserId: String, - targetRoomId: String?, - createCallId: String?, - awaitCallId: String?) { + override suspend fun transfer( + targetUserId: String, + targetRoomId: String?, + createCallId: String?, + awaitCallId: String? + ) { val profileInfoParams = GetProfileInfoTask.Params(targetUserId) val profileInfo = try { getProfileInfoTask.execute(profileInfoParams) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/content/DefaultContentUrlResolver.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/content/DefaultContentUrlResolver.kt index 5d77424482..ad2b9d0905 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/content/DefaultContentUrlResolver.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/content/DefaultContentUrlResolver.kt @@ -80,9 +80,11 @@ internal class DefaultContentUrlResolver @Inject constructor( } } - private fun resolve(contentUrl: String, - toThumbnail: Boolean, - params: String = ""): String { + private fun resolve( + contentUrl: String, + toThumbnail: Boolean, + params: String = "" + ): String { var serverAndMediaId = contentUrl.removeMxcPrefix() val apiPath = if (scannerService.isScannerEnabled()) { 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 f96a019fe2..c023646c7f 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 @@ -62,10 +62,12 @@ internal class FileUploader @Inject constructor( private val uploadUrl = contentUrlResolver.uploadUrl private val responseAdapter = moshi.adapter(ContentUploadResponse::class.java) - suspend fun uploadFile(file: File, - filename: String?, - mimeType: String?, - progressListener: ProgressRequestBody.Listener? = null): ContentUploadResponse { + suspend fun uploadFile( + file: File, + filename: String?, + mimeType: String?, + progressListener: ProgressRequestBody.Listener? = null + ): ContentUploadResponse { // Check size limit val maxUploadFileSize = homeServerCapabilitiesService.getHomeServerCapabilities().maxUploadFileSize @@ -99,18 +101,22 @@ internal class FileUploader @Inject constructor( return upload(uploadBody, filename, progressListener) } - suspend fun uploadByteArray(byteArray: ByteArray, - filename: String?, - mimeType: String?, - progressListener: ProgressRequestBody.Listener? = null): ContentUploadResponse { + suspend fun uploadByteArray( + byteArray: ByteArray, + filename: String?, + mimeType: String?, + progressListener: ProgressRequestBody.Listener? = null + ): ContentUploadResponse { val uploadBody = byteArray.toRequestBody(mimeType?.toMediaTypeOrNull()) return upload(uploadBody, filename, progressListener) } - suspend fun uploadFromUri(uri: Uri, - filename: String?, - mimeType: String?, - progressListener: ProgressRequestBody.Listener? = null): ContentUploadResponse { + suspend fun uploadFromUri( + uri: Uri, + filename: String?, + mimeType: String?, + progressListener: ProgressRequestBody.Listener? = null + ): ContentUploadResponse { val workingFile = context.copyUriToTempFile(uri) return uploadFile(workingFile, filename, mimeType, progressListener).also { tryOrNull { workingFile.delete() } @@ -128,9 +134,11 @@ internal class FileUploader @Inject constructor( } } - private suspend fun upload(uploadBody: RequestBody, - filename: String?, - progressListener: ProgressRequestBody.Listener?): ContentUploadResponse { + private suspend fun upload( + uploadBody: RequestBody, + filename: String?, + progressListener: ProgressRequestBody.Listener? + ): ContentUploadResponse { val urlBuilder = uploadUrl.toHttpUrlOrNull()?.newBuilder() ?: throw RuntimeException() val httpUrl = urlBuilder 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 c5aa6cd5e7..bc04cac35a 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 @@ -35,7 +35,8 @@ internal class ImageCompressor @Inject constructor( imageFile: File, desiredWidth: Int, desiredHeight: Int, - desiredQuality: Int = 80): File { + desiredQuality: Int = 80 + ): File { return withContext(coroutineDispatchers.io) { val compressedBitmap = BitmapFactory.Options().run { inJustDecodeBounds = true @@ -68,20 +69,20 @@ 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) } - else -> return bitmap + else -> return bitmap } return Bitmap.createBitmap(bitmap, 0, 0, bitmap.width, bitmap.height, matrix, true) } 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 5a00c4b5b4..1e62b5d7f5 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 @@ -192,7 +192,7 @@ internal class UploadContentWorker(val context: Context, params: WorkerParameter }) .let { videoCompressionResult -> when (videoCompressionResult) { - is VideoCompressionResult.Success -> { + is VideoCompressionResult.Success -> { val compressedFile = videoCompressionResult.compressedFile var compressedWidth: Int? = null var compressedHeight: Int? = null @@ -375,12 +375,14 @@ internal class UploadContentWorker(val context: Context, params: WorkerParameter ) } - private suspend fun handleSuccess(params: Params, - attachmentUrl: String, - encryptedFileInfo: EncryptedFileInfo?, - thumbnailUrl: String?, - thumbnailEncryptedFileInfo: EncryptedFileInfo?, - newAttachmentAttributes: NewAttachmentAttributes): Result { + private suspend fun handleSuccess( + params: Params, + attachmentUrl: String, + encryptedFileInfo: EncryptedFileInfo?, + thumbnailUrl: String?, + thumbnailEncryptedFileInfo: EncryptedFileInfo?, + newAttachmentAttributes: NewAttachmentAttributes + ): Result { notifyTracker(params) { contentUploadStateTracker.setSuccess(it) } params.localEchoIds.forEach { updateEvent(it.eventId, attachmentUrl, encryptedFileInfo, thumbnailUrl, thumbnailEncryptedFileInfo, newAttachmentAttributes) @@ -396,20 +398,22 @@ internal class UploadContentWorker(val context: Context, params: WorkerParameter } } - private suspend fun updateEvent(eventId: String, - url: String, - encryptedFileInfo: EncryptedFileInfo?, - thumbnailUrl: String? = null, - thumbnailEncryptedFileInfo: EncryptedFileInfo?, - newAttachmentAttributes: NewAttachmentAttributes) { + private suspend fun updateEvent( + eventId: String, + url: String, + encryptedFileInfo: EncryptedFileInfo?, + thumbnailUrl: String? = null, + thumbnailEncryptedFileInfo: EncryptedFileInfo?, + newAttachmentAttributes: NewAttachmentAttributes + ) { localEchoRepository.updateEcho(eventId) { _, event -> val messageContent: MessageContent? = event.asDomain().content.toModel() val updatedContent = when (messageContent) { is MessageImageContent -> messageContent.update(url, encryptedFileInfo, newAttachmentAttributes) is MessageVideoContent -> messageContent.update(url, encryptedFileInfo, thumbnailUrl, thumbnailEncryptedFileInfo, newAttachmentAttributes) - is MessageFileContent -> messageContent.update(url, encryptedFileInfo, newAttachmentAttributes.newFileSize) + is MessageFileContent -> messageContent.update(url, encryptedFileInfo, newAttachmentAttributes.newFileSize) is MessageAudioContent -> messageContent.update(url, encryptedFileInfo, newAttachmentAttributes.newFileSize) - else -> messageContent + else -> messageContent } event.content = ContentMapper.map(updatedContent.toContent()) } @@ -419,9 +423,11 @@ internal class UploadContentWorker(val context: Context, params: WorkerParameter params.localEchoIds.forEach { function.invoke(it.eventId) } } - private fun MessageImageContent.update(url: String, - encryptedFileInfo: EncryptedFileInfo?, - newAttachmentAttributes: NewAttachmentAttributes?): MessageImageContent { + private fun MessageImageContent.update( + url: String, + encryptedFileInfo: EncryptedFileInfo?, + newAttachmentAttributes: NewAttachmentAttributes? + ): MessageImageContent { return copy( url = if (encryptedFileInfo == null) url else null, encryptedFileInfo = encryptedFileInfo?.copy(url = url), @@ -433,11 +439,13 @@ internal class UploadContentWorker(val context: Context, params: WorkerParameter ) } - private fun MessageVideoContent.update(url: String, - encryptedFileInfo: EncryptedFileInfo?, - thumbnailUrl: String?, - thumbnailEncryptedFileInfo: EncryptedFileInfo?, - newAttachmentAttributes: NewAttachmentAttributes?): MessageVideoContent { + private fun MessageVideoContent.update( + url: String, + encryptedFileInfo: EncryptedFileInfo?, + thumbnailUrl: String?, + thumbnailEncryptedFileInfo: EncryptedFileInfo?, + newAttachmentAttributes: NewAttachmentAttributes? + ): MessageVideoContent { return copy( url = if (encryptedFileInfo == null) url else null, encryptedFileInfo = encryptedFileInfo?.copy(url = url), @@ -451,9 +459,11 @@ internal class UploadContentWorker(val context: Context, params: WorkerParameter ) } - private fun MessageFileContent.update(url: String, - encryptedFileInfo: EncryptedFileInfo?, - size: Long): MessageFileContent { + private fun MessageFileContent.update( + url: String, + encryptedFileInfo: EncryptedFileInfo?, + size: Long + ): MessageFileContent { return copy( url = if (encryptedFileInfo == null) url else null, encryptedFileInfo = encryptedFileInfo?.copy(url = url), @@ -461,9 +471,11 @@ internal class UploadContentWorker(val context: Context, params: WorkerParameter ) } - private fun MessageAudioContent.update(url: String, - encryptedFileInfo: EncryptedFileInfo?, - size: Long): MessageAudioContent { + private fun MessageAudioContent.update( + url: String, + encryptedFileInfo: EncryptedFileInfo?, + size: Long + ): MessageAudioContent { return copy( url = if (encryptedFileInfo == null) url else null, encryptedFileInfo = encryptedFileInfo?.copy(url = url), diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/content/VideoCompressor.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/content/VideoCompressor.kt index a43f8abf33..5c00927e23 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/content/VideoCompressor.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/content/VideoCompressor.kt @@ -32,8 +32,10 @@ internal class VideoCompressor @Inject constructor( private val temporaryFileCreator: TemporaryFileCreator ) { - suspend fun compress(videoFile: File, - progressListener: ProgressListener?): VideoCompressionResult { + suspend fun compress( + videoFile: File, + progressListener: ProgressListener? + ): VideoCompressionResult { val destinationFile = temporaryFileCreator.create() val job = Job() @@ -111,7 +113,7 @@ internal class VideoCompressor @Inject constructor( deleteFile(destinationFile) VideoCompressionResult.CompressionNotNeeded } - else -> { + else -> { // Should not happen... // Delete now the temporary file deleteFile(destinationFile) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/contentscanner/ContentScannerModule.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/contentscanner/ContentScannerModule.kt index 7ea74225cd..89b5c44ef0 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/contentscanner/ContentScannerModule.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/contentscanner/ContentScannerModule.kt @@ -49,9 +49,11 @@ internal abstract class ContentScannerModule { @Provides @ContentScannerDatabase @SessionScope - fun providesContentScannerRealmConfiguration(realmKeysUtils: RealmKeysUtils, - @SessionFilesDirectory directory: File, - @UserMd5 userMd5: String): RealmConfiguration { + fun providesContentScannerRealmConfiguration( + realmKeysUtils: RealmKeysUtils, + @SessionFilesDirectory directory: File, + @UserMd5 userMd5: String + ): RealmConfiguration { return RealmConfiguration.Builder() .directory(directory) .name("matrix-sdk-content-scanning.realm") 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 e4b64a1a0e..bb3051bc96 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,10 +31,12 @@ internal fun ContentScanResultEntity.Companion.get(realm: Realm, attachmentUrl: .findFirst() } -internal fun ContentScanResultEntity.Companion.getOrCreate(realm: Realm, - attachmentUrl: String, - contentScannerUrl: String?, - currentTimeMillis: Long): 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 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 27729d38c7..72dc15c1bd 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 @@ -106,7 +106,7 @@ internal class RealmContentScannerStore @Inject constructor( ScanState.IN_PROGRESS, ScanState.TRUSTED, ScanState.INFECTED -> true - else -> false + else -> false } } return isKnown diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/directory/DirectoryAPI.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/directory/DirectoryAPI.kt index 0db6812609..488c61206b 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/directory/DirectoryAPI.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/directory/DirectoryAPI.kt @@ -49,8 +49,10 @@ internal interface DirectoryAPI { * @param body the body containing the new directory visibility */ @PUT(NetworkConstants.URI_API_PREFIX_PATH_R0 + "directory/list/room/{roomId}") - suspend fun setRoomDirectoryVisibility(@Path("roomId") roomId: String, - @Body body: RoomDirectoryVisibilityJson) + suspend fun setRoomDirectoryVisibility( + @Path("roomId") roomId: String, + @Body body: RoomDirectoryVisibilityJson + ) /** * Add alias to the room. @@ -58,8 +60,10 @@ internal interface DirectoryAPI { * @param body the Json body */ @PUT(NetworkConstants.URI_API_PREFIX_PATH_R0 + "directory/room/{roomAlias}") - suspend fun addRoomAlias(@Path("roomAlias") roomAlias: String, - @Body body: AddRoomAliasBody) + suspend fun addRoomAlias( + @Path("roomAlias") roomAlias: String, + @Body body: AddRoomAliasBody + ) /** * Delete a room alias. diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/download/ProgressResponseBody.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/download/ProgressResponseBody.kt index 4fd4fda7d1..4e0096b7dd 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/download/ProgressResponseBody.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/download/ProgressResponseBody.kt @@ -27,7 +27,8 @@ import okio.buffer internal class ProgressResponseBody( private val responseBody: ResponseBody, private val chainUrl: String, - private val progressListener: ProgressListener) : ResponseBody() { + private val progressListener: ProgressListener +) : ResponseBody() { private var bufferedSource: BufferedSource? = null diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/filter/DefaultFilterService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/filter/DefaultFilterService.kt index 9a16b8fd4a..2e68d02d8c 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/filter/DefaultFilterService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/filter/DefaultFilterService.kt @@ -21,8 +21,10 @@ import org.matrix.android.sdk.internal.task.TaskExecutor import org.matrix.android.sdk.internal.task.configureWith import javax.inject.Inject -internal class DefaultFilterService @Inject constructor(private val saveFilterTask: SaveFilterTask, - private val taskExecutor: TaskExecutor) : FilterService { +internal class DefaultFilterService @Inject constructor( + private val saveFilterTask: SaveFilterTask, + private val taskExecutor: TaskExecutor +) : FilterService { // TODO Pass a list of support events instead override fun setFilter(filterPreset: FilterService.FilterPreset) { diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/filter/FilterApi.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/filter/FilterApi.kt index d1df77d14a..ac1e9a654c 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/filter/FilterApi.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/filter/FilterApi.kt @@ -31,8 +31,10 @@ internal interface FilterApi { * @param body the Json representation of a FilterBody object */ @POST(NetworkConstants.URI_API_PREFIX_PATH_R0 + "user/{userId}/filter") - suspend fun uploadFilter(@Path("userId") userId: String, - @Body body: Filter): FilterResponse + suspend fun uploadFilter( + @Path("userId") userId: String, + @Body body: Filter + ): FilterResponse /** * Gets a filter with a given filterId from the homeserver. @@ -42,6 +44,8 @@ internal interface FilterApi { * @return Filter */ @GET(NetworkConstants.URI_API_PREFIX_PATH_R0 + "user/{userId}/filter/{filterId}") - suspend fun getFilterById(@Path("userId") userId: String, - @Path("filterId") filterId: String): Filter + suspend fun getFilterById( + @Path("userId") userId: String, + @Path("filterId") filterId: String + ): Filter } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/filter/SaveFilterTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/filter/SaveFilterTask.kt index e9d5b876a8..63afa1bbbc 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/filter/SaveFilterTask.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/filter/SaveFilterTask.kt @@ -45,7 +45,7 @@ internal class DefaultSaveFilterTask @Inject constructor( FilterService.FilterPreset.ElementFilter -> { FilterFactory.createElementFilter() } - FilterService.FilterPreset.NoFilter -> { + FilterService.FilterPreset.NoFilter -> { FilterFactory.createDefaultFilter() } } @@ -53,7 +53,7 @@ internal class DefaultSaveFilterTask @Inject constructor( FilterService.FilterPreset.ElementFilter -> { FilterFactory.createElementRoomFilter() } - FilterService.FilterPreset.NoFilter -> { + FilterService.FilterPreset.NoFilter -> { FilterFactory.createDefaultRoomFilter() } } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/group/DefaultGroup.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/group/DefaultGroup.kt index 4f610fd81b..9c37d4db6c 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/group/DefaultGroup.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/group/DefaultGroup.kt @@ -18,8 +18,10 @@ package org.matrix.android.sdk.internal.session.group import org.matrix.android.sdk.api.session.group.Group -internal class DefaultGroup(override val groupId: String, - private val getGroupDataTask: GetGroupDataTask) : Group { +internal class DefaultGroup( + override val groupId: String, + private val getGroupDataTask: GetGroupDataTask +) : Group { override suspend fun fetchGroupData() { val params = GetGroupDataTask.Params.FetchWithIds(listOf(groupId)) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/group/GetGroupDataTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/group/GetGroupDataTask.kt index 4e0ee3422b..235291d061 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/group/GetGroupDataTask.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/group/GetGroupDataTask.kt @@ -58,7 +58,7 @@ internal class DefaultGetGroupDataTask @Inject constructor( is GetGroupDataTask.Params.FetchAllActive -> { getActiveGroupIds() } - is GetGroupDataTask.Params.FetchWithIds -> { + is GetGroupDataTask.Params.FetchWithIds -> { params.groupIds } } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/homeserver/GetHomeServerCapabilitiesTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/homeserver/GetHomeServerCapabilitiesTask.kt index b1a518724c..add69dd8c7 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/homeserver/GetHomeServerCapabilitiesTask.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/homeserver/GetHomeServerCapabilitiesTask.kt @@ -24,6 +24,7 @@ import org.matrix.android.sdk.api.extensions.orFalse import org.matrix.android.sdk.api.extensions.orTrue import org.matrix.android.sdk.api.session.homeserver.HomeServerCapabilities import org.matrix.android.sdk.internal.auth.version.Versions +import org.matrix.android.sdk.internal.auth.version.doesServerSupportLogoutDevices import org.matrix.android.sdk.internal.auth.version.doesServerSupportThreads import org.matrix.android.sdk.internal.auth.version.isLoginAndRegistrationSupportedBySdk import org.matrix.android.sdk.internal.database.model.HomeServerCapabilitiesEntity @@ -109,10 +110,12 @@ internal class DefaultGetHomeServerCapabilitiesTask @Inject constructor( insertInDb(capabilities, mediaConfig, versions, wellknownResult) } - private suspend fun insertInDb(getCapabilitiesResult: GetCapabilitiesResult?, - getMediaConfigResult: GetMediaConfigResult?, - getVersionResult: Versions?, - getWellknownResult: WellknownResult?) { + private suspend fun insertInDb( + getCapabilitiesResult: GetCapabilitiesResult?, + getMediaConfigResult: GetMediaConfigResult?, + getVersionResult: Versions?, + getWellknownResult: WellknownResult? + ) { monarchy.awaitTransaction { realm -> val homeServerCapabilitiesEntity = HomeServerCapabilitiesEntity.getOrCreate(realm) @@ -140,6 +143,7 @@ internal class DefaultGetHomeServerCapabilitiesTask @Inject constructor( if (getVersionResult != null) { homeServerCapabilitiesEntity.lastVersionIdentityServerSupported = getVersionResult.isLoginAndRegistrationSupportedBySdk() + homeServerCapabilitiesEntity.canControlLogoutDevices = getVersionResult.doesServerSupportLogoutDevices() } if (getWellknownResult != null && getWellknownResult is WellknownResult.Prompt) { diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/homeserver/HomeServerPinger.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/homeserver/HomeServerPinger.kt index 70e1e551aa..a3d6b7fa49 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/homeserver/HomeServerPinger.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/homeserver/HomeServerPinger.kt @@ -22,8 +22,10 @@ import org.matrix.android.sdk.internal.network.executeRequest import org.matrix.android.sdk.internal.task.TaskExecutor import javax.inject.Inject -internal class HomeServerPinger @Inject constructor(private val taskExecutor: TaskExecutor, - private val capabilitiesAPI: CapabilitiesAPI) { +internal class HomeServerPinger @Inject constructor( + private val taskExecutor: TaskExecutor, + private val capabilitiesAPI: CapabilitiesAPI +) { fun canReachHomeServer(callback: (Boolean) -> Unit) { taskExecutor.executorScope.launch { diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/identity/DefaultIdentityService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/identity/DefaultIdentityService.kt index 74838afc65..30c7773a8e 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/identity/DefaultIdentityService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/identity/DefaultIdentityService.kt @@ -283,8 +283,8 @@ internal class DefaultIdentityService @Inject constructor( identityStore.setToken(null) lookUpInternal(false, threePids) } - throwable.isTermsNotSigned() -> throw IdentityServiceError.TermsNotSignedException - else -> throw throwable + throwable.isTermsNotSigned() -> throw IdentityServiceError.TermsNotSignedException + else -> throw throwable } } } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/identity/IdentityAPI.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/identity/IdentityAPI.kt index aef86ed08e..7b04bc52b0 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/identity/IdentityAPI.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/identity/IdentityAPI.kt @@ -95,8 +95,10 @@ internal interface IdentityAPI { * - https://matrix.org/docs/spec/identity_service/latest#post-matrix-identity-v2-validate-email-submittoken */ @POST(NetworkConstants.URI_IDENTITY_PATH_V2 + "validate/{medium}/submitToken") - suspend fun submitToken(@Path("medium") medium: String, - @Body body: IdentityRequestOwnershipParams): SuccessResult + suspend fun submitToken( + @Path("medium") medium: String, + @Body body: IdentityRequestOwnershipParams + ): SuccessResult /** * https://matrix.org/docs/spec/identity_service/r0.3.0#post-matrix-identity-v2-sign-ed25519. diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/identity/IdentityBulkLookupTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/identity/IdentityBulkLookupTask.kt index f642ed4cf2..21b59cca9d 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/identity/IdentityBulkLookupTask.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/identity/IdentityBulkLookupTask.kt @@ -75,10 +75,12 @@ internal class DefaultIdentityBulkLookupTask @Inject constructor( val identityLookUpResponse: IdentityLookUpResponse ) - private suspend fun lookUpInternal(identityAPI: IdentityAPI, - threePids: List, - hashDetailResponse: IdentityHashDetailResponse, - canRetry: Boolean): LookUpData { + private suspend fun lookUpInternal( + identityAPI: IdentityAPI, + threePids: List, + hashDetailResponse: IdentityHashDetailResponse, + canRetry: Boolean + ): LookUpData { val hashedAddresses = getHashedAddresses(threePids, hashDetailResponse.pepper) return try { LookUpData(hashedAddresses, diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/identity/IdentityModule.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/identity/IdentityModule.kt index 4e9d7dc7f7..33d8164895 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/identity/IdentityModule.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/identity/IdentityModule.kt @@ -21,6 +21,7 @@ import dagger.Module import dagger.Provides import io.realm.RealmConfiguration import okhttp3.OkHttpClient +import org.matrix.android.sdk.api.MatrixConfiguration import org.matrix.android.sdk.api.session.identity.IdentityService import org.matrix.android.sdk.internal.database.RealmKeysUtils import org.matrix.android.sdk.internal.di.AuthenticatedIdentity @@ -29,6 +30,7 @@ import org.matrix.android.sdk.internal.di.SessionFilesDirectory import org.matrix.android.sdk.internal.di.UnauthenticatedWithCertificate import org.matrix.android.sdk.internal.di.UserMd5 import org.matrix.android.sdk.internal.network.httpclient.addAccessTokenInterceptor +import org.matrix.android.sdk.internal.network.httpclient.applyMatrixConfiguration import org.matrix.android.sdk.internal.network.token.AccessTokenProvider import org.matrix.android.sdk.internal.session.SessionModule import org.matrix.android.sdk.internal.session.SessionScope @@ -47,11 +49,15 @@ internal abstract class IdentityModule { @Provides @SessionScope @AuthenticatedIdentity - fun providesOkHttpClient(@UnauthenticatedWithCertificate okHttpClient: OkHttpClient, - @AuthenticatedIdentity accessTokenProvider: AccessTokenProvider): OkHttpClient { + fun providesOkHttpClient( + @UnauthenticatedWithCertificate okHttpClient: OkHttpClient, + @AuthenticatedIdentity accessTokenProvider: AccessTokenProvider, + matrixConfiguration: MatrixConfiguration, + ): OkHttpClient { return okHttpClient .newBuilder() .addAccessTokenInterceptor(accessTokenProvider) + .applyMatrixConfiguration(matrixConfiguration) .build() } @@ -59,10 +65,12 @@ internal abstract class IdentityModule { @Provides @IdentityDatabase @SessionScope - fun providesIdentityRealmConfiguration(realmKeysUtils: RealmKeysUtils, - realmIdentityStoreMigration: RealmIdentityStoreMigration, - @SessionFilesDirectory directory: File, - @UserMd5 userMd5: String): RealmConfiguration { + fun providesIdentityRealmConfiguration( + realmKeysUtils: RealmKeysUtils, + realmIdentityStoreMigration: RealmIdentityStoreMigration, + @SessionFilesDirectory directory: File, + @UserMd5 userMd5: String + ): RealmConfiguration { return RealmConfiguration.Builder() .directory(directory) .name("matrix-sdk-identity.realm") diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/identity/IdentityRequestTokenForBindingTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/identity/IdentityRequestTokenForBindingTask.kt index fe12309650..02462387fa 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/identity/IdentityRequestTokenForBindingTask.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/identity/IdentityRequestTokenForBindingTask.kt @@ -57,7 +57,7 @@ internal class DefaultIdentityRequestTokenForBindingTask @Inject constructor( val tokenResponse = executeRequest(null) { when (params.threePid) { - is ThreePid.Email -> identityAPI.requestTokenToBindEmail( + is ThreePid.Email -> identityAPI.requestTokenToBindEmail( IdentityRequestTokenForEmailBody( clientSecret = clientSecret, sendAttempt = sendAttempt, diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/identity/db/IdentityDataEntityQuery.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/identity/db/IdentityDataEntityQuery.kt index aecf21678c..08e5b43977 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/identity/db/IdentityDataEntityQuery.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/identity/db/IdentityDataEntityQuery.kt @@ -32,8 +32,10 @@ private fun IdentityDataEntity.Companion.getOrCreate(realm: Realm): IdentityData return get(realm) ?: realm.createObject() } -internal fun IdentityDataEntity.Companion.setUrl(realm: Realm, - url: String?) { +internal fun IdentityDataEntity.Companion.setUrl( + realm: Realm, + url: String? +) { realm.where().findAll().deleteAllFromRealm() // Delete all pending binding if any IdentityPendingBindingEntity.deleteAll(realm) @@ -45,23 +47,29 @@ internal fun IdentityDataEntity.Companion.setUrl(realm: Realm, } } -internal fun IdentityDataEntity.Companion.setToken(realm: Realm, - newToken: String?) { +internal fun IdentityDataEntity.Companion.setToken( + realm: Realm, + newToken: String? +) { get(realm)?.apply { token = newToken } } -internal fun IdentityDataEntity.Companion.setUserConsent(realm: Realm, - newConsent: Boolean) { +internal fun IdentityDataEntity.Companion.setUserConsent( + realm: Realm, + newConsent: Boolean +) { get(realm)?.apply { userConsent = newConsent } } -internal fun IdentityDataEntity.Companion.setHashDetails(realm: Realm, - pepper: String, - algorithms: List) { +internal fun IdentityDataEntity.Companion.setHashDetails( + realm: Realm, + pepper: String, + algorithms: List +) { get(realm)?.apply { hashLookupPepper = pepper hashLookupAlgorithm = RealmList().apply { addAll(algorithms) } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/initsync/Extensions.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/initsync/Extensions.kt deleted file mode 100644 index b40b1a56bf..0000000000 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/initsync/Extensions.kt +++ /dev/null @@ -1,45 +0,0 @@ -/* - * 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.session.initsync - -import org.matrix.android.sdk.api.session.initsync.InitSyncStep - -internal inline fun reportSubtask(reporter: ProgressReporter?, - initSyncStep: InitSyncStep, - totalProgress: Int, - parentWeight: Float, - block: () -> T): T { - reporter?.startTask(initSyncStep, totalProgress, parentWeight) - return block().also { - reporter?.endTask() - } -} - -internal inline fun Map.mapWithProgress(reporter: ProgressReporter?, - initSyncStep: InitSyncStep, - parentWeight: Float, - transform: (Map.Entry) -> R): List { - var current = 0F - reporter?.startTask(initSyncStep, count() + 1, parentWeight) - return map { - reporter?.reportProgress(current) - current++ - transform.invoke(it) - }.also { - reporter?.endTask() - } -} 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 8034e5b974..7588037b46 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 @@ -54,11 +54,13 @@ import javax.inject.Inject * */ @SessionScope -internal class IntegrationManager @Inject constructor(matrixConfiguration: MatrixConfiguration, - @SessionDatabase private val monarchy: Monarchy, - private val updateUserAccountDataTask: UpdateUserAccountDataTask, - private val accountDataDataSource: UserAccountDataDataSource, - private val widgetFactory: WidgetFactory) : +internal class IntegrationManager @Inject constructor( + matrixConfiguration: MatrixConfiguration, + @SessionDatabase private val monarchy: Monarchy, + private val updateUserAccountDataTask: UpdateUserAccountDataTask, + private val accountDataDataSource: UserAccountDataDataSource, + private val widgetFactory: WidgetFactory +) : SessionLifecycleObserver { private val currentConfigs = ArrayList() diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/media/GetPreviewUrlTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/media/GetPreviewUrlTask.kt index 32bcf3f7ca..a31f0d6065 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/media/GetPreviewUrlTask.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/media/GetPreviewUrlTask.kt @@ -48,8 +48,8 @@ internal class DefaultGetPreviewUrlTask @Inject constructor( override suspend fun execute(params: GetPreviewUrlTask.Params): PreviewUrlData { return when (params.cacheStrategy) { - CacheStrategy.NoCache -> doRequest(params.url, params.timestamp) - is CacheStrategy.TtlCache -> doRequestWithCache( + CacheStrategy.NoCache -> doRequest(params.url, params.timestamp) + is CacheStrategy.TtlCache -> doRequestWithCache( params.url, params.timestamp, params.cacheStrategy.validityDurationInMillis, diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/openid/GetOpenIdTokenTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/openid/GetOpenIdTokenTask.kt index a6ad025b8d..5ab6aca88c 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/openid/GetOpenIdTokenTask.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/openid/GetOpenIdTokenTask.kt @@ -28,7 +28,8 @@ internal interface GetOpenIdTokenTask : Task internal class DefaultGetOpenIdTokenTask @Inject constructor( @UserId private val userId: String, private val openIdAPI: OpenIdAPI, - private val globalErrorReceiver: GlobalErrorReceiver) : GetOpenIdTokenTask { + private val globalErrorReceiver: GlobalErrorReceiver +) : GetOpenIdTokenTask { override suspend fun execute(params: Unit): OpenIdToken { return executeRequest(globalErrorReceiver) { diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/openid/OpenIdAPI.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/openid/OpenIdAPI.kt index c3caaefdec..180ecbb1a3 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/openid/OpenIdAPI.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/openid/OpenIdAPI.kt @@ -35,6 +35,8 @@ internal interface OpenIdAPI { * @param body an empty json body */ @POST(NetworkConstants.URI_API_PREFIX_PATH_R0 + "user/{userId}/openid/request_token") - suspend fun openIdToken(@Path("userId") userId: String, - @Body body: JsonDict = emptyMap()): OpenIdToken + suspend fun openIdToken( + @Path("userId") userId: String, + @Body body: JsonDict = emptyMap() + ): OpenIdToken } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/permalinks/PermalinkFactory.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/permalinks/PermalinkFactory.kt index f9da7b66f6..8e20199135 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/permalinks/PermalinkFactory.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/permalinks/PermalinkFactory.kt @@ -44,15 +44,15 @@ internal class PermalinkFactory @Inject constructor( fun createPermalink(id: String, forceMatrixTo: Boolean): String? { return when { - id.isEmpty() -> null + id.isEmpty() -> null !useClientFormat(forceMatrixTo) -> MATRIX_TO_URL_BASE + escape(id) - else -> { + else -> { buildString { append(matrixConfiguration.clientPermalinkBaseUrl) when { MatrixPatterns.isRoomId(id) || MatrixPatterns.isRoomAlias(id) -> append(ROOM_PATH) - MatrixPatterns.isUserId(id) -> append(USER_PATH) - MatrixPatterns.isGroupId(id) -> append(GROUP_PATH) + MatrixPatterns.isUserId(id) -> append(USER_PATH) + MatrixPatterns.isGroupId(id) -> append(GROUP_PATH) } append(escape(id)) } @@ -94,16 +94,16 @@ internal class PermalinkFactory @Inject constructor( fun getLinkedId(url: String): String? { val clientBaseUrl = matrixConfiguration.clientPermalinkBaseUrl return when { - url.startsWith(MATRIX_TO_URL_BASE) -> url.substring(MATRIX_TO_URL_BASE.length) + url.startsWith(MATRIX_TO_URL_BASE) -> url.substring(MATRIX_TO_URL_BASE.length) clientBaseUrl != null && url.startsWith(clientBaseUrl) -> { when (PermalinkParser.parse(url)) { is PermalinkData.GroupLink -> url.substring(clientBaseUrl.length + GROUP_PATH.length) - is PermalinkData.RoomLink -> url.substring(clientBaseUrl.length + ROOM_PATH.length) - is PermalinkData.UserLink -> url.substring(clientBaseUrl.length + USER_PATH.length) - else -> null + is PermalinkData.RoomLink -> url.substring(clientBaseUrl.length + ROOM_PATH.length) + is PermalinkData.UserLink -> url.substring(clientBaseUrl.length + USER_PATH.length) + else -> null } } - else -> null + else -> null } ?.substringBeforeLast("?") } @@ -111,7 +111,7 @@ internal class PermalinkFactory @Inject constructor( fun createMentionSpanTemplate(type: PermalinkService.SpanTemplateType, forceMatrixTo: Boolean): String { return buildString { when (type) { - HTML -> append(MENTION_SPAN_TO_HTML_TEMPLATE_BEGIN) + HTML -> append(MENTION_SPAN_TO_HTML_TEMPLATE_BEGIN) MARKDOWN -> append(MENTION_SPAN_TO_MD_TEMPLATE_BEGIN) } append(baseUrl(forceMatrixTo)) @@ -119,7 +119,7 @@ internal class PermalinkFactory @Inject constructor( append(USER_PATH) } when (type) { - HTML -> append(MENTION_SPAN_TO_HTML_TEMPLATE_END) + HTML -> append(MENTION_SPAN_TO_HTML_TEMPLATE_END) MARKDOWN -> append(MENTION_SPAN_TO_MD_TEMPLATE_END) } } 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 edc45fe945..5fb20bb259 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 @@ -101,7 +101,7 @@ internal class ViaParameterFinder @Inject constructor( } fun userCanInvite(userId: String, roomId: String): Boolean { - val powerLevelsHelper = stateEventDataSource.getStateEvent(roomId, EventType.STATE_ROOM_POWER_LEVELS, QueryStringValue.NoCondition) + val powerLevelsHelper = stateEventDataSource.getStateEvent(roomId, EventType.STATE_ROOM_POWER_LEVELS, QueryStringValue.IsEmpty) ?.content?.toModel() ?.let { PowerLevelsHelper(it) } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/presence/PresenceAPI.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/presence/PresenceAPI.kt index 53d0d5e963..4af0307cb5 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/presence/PresenceAPI.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/presence/PresenceAPI.kt @@ -31,8 +31,10 @@ internal interface PresenceAPI { * Ref: https://matrix.org/docs/spec/client_server/latest#put-matrix-client-r0-presence-userid-status */ @PUT(NetworkConstants.URI_API_PREFIX_PATH_R0 + "presence/{userId}/status") - suspend fun setPresence(@Path("userId") userId: String, - @Body body: SetPresenceBody) + suspend fun setPresence( + @Path("userId") userId: String, + @Body body: SetPresenceBody + ) /** * Get the given user's presence state. diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/profile/AddThreePidTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/profile/AddThreePidTask.kt index 678d399428..4a6cb6c019 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/profile/AddThreePidTask.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/profile/AddThreePidTask.kt @@ -37,11 +37,12 @@ internal class DefaultAddThreePidTask @Inject constructor( private val profileAPI: ProfileAPI, @SessionDatabase private val monarchy: Monarchy, private val pendingThreePidMapper: PendingThreePidMapper, - private val globalErrorReceiver: GlobalErrorReceiver) : AddThreePidTask() { + private val globalErrorReceiver: GlobalErrorReceiver +) : AddThreePidTask() { override suspend fun execute(params: Params) { when (params.threePid) { - is ThreePid.Email -> addEmail(params.threePid) + is ThreePid.Email -> addEmail(params.threePid) is ThreePid.Msisdn -> addMsisdn(params.threePid) } } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/profile/BindThreePidsTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/profile/BindThreePidsTask.kt index f630c2c225..5e892b32c3 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/profile/BindThreePidsTask.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/profile/BindThreePidsTask.kt @@ -33,11 +33,12 @@ internal abstract class BindThreePidsTask : Task ) } -internal class DefaultBindThreePidsTask @Inject constructor(private val profileAPI: ProfileAPI, - private val identityStore: IdentityStore, - @AuthenticatedIdentity - private val accessTokenProvider: AccessTokenProvider, - private val globalErrorReceiver: GlobalErrorReceiver) : BindThreePidsTask() { +internal class DefaultBindThreePidsTask @Inject constructor( + private val profileAPI: ProfileAPI, + private val identityStore: IdentityStore, + @AuthenticatedIdentity private val accessTokenProvider: AccessTokenProvider, + private val globalErrorReceiver: GlobalErrorReceiver +) : BindThreePidsTask() { override suspend fun execute(params: Params) { val identityServerUrlWithoutProtocol = identityStore.getIdentityServerUrlWithoutProtocol() ?: throw IdentityServiceError.NoIdentityServerConfigured val identityServerAccessToken = accessTokenProvider.getToken() ?: throw IdentityServiceError.NoIdentityServerConfigured diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/profile/DefaultProfileService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/profile/DefaultProfileService.kt index 5e64a6af0e..0776569661 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/profile/DefaultProfileService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/profile/DefaultProfileService.kt @@ -38,20 +38,22 @@ import org.matrix.android.sdk.internal.task.TaskExecutor import org.matrix.android.sdk.internal.task.configureWith import javax.inject.Inject -internal class DefaultProfileService @Inject constructor(private val taskExecutor: TaskExecutor, - @SessionDatabase private val monarchy: Monarchy, - private val coroutineDispatchers: MatrixCoroutineDispatchers, - private val refreshUserThreePidsTask: RefreshUserThreePidsTask, - private val getProfileInfoTask: GetProfileInfoTask, - private val setDisplayNameTask: SetDisplayNameTask, - private val setAvatarUrlTask: SetAvatarUrlTask, - private val addThreePidTask: AddThreePidTask, - private val validateSmsCodeTask: ValidateSmsCodeTask, - private val finalizeAddingThreePidTask: FinalizeAddingThreePidTask, - private val deleteThreePidTask: DeleteThreePidTask, - private val pendingThreePidMapper: PendingThreePidMapper, - private val userStore: UserStore, - private val fileUploader: FileUploader) : ProfileService { +internal class DefaultProfileService @Inject constructor( + private val taskExecutor: TaskExecutor, + @SessionDatabase private val monarchy: Monarchy, + private val coroutineDispatchers: MatrixCoroutineDispatchers, + private val refreshUserThreePidsTask: RefreshUserThreePidsTask, + private val getProfileInfoTask: GetProfileInfoTask, + private val setDisplayNameTask: SetDisplayNameTask, + private val setAvatarUrlTask: SetAvatarUrlTask, + private val addThreePidTask: AddThreePidTask, + private val validateSmsCodeTask: ValidateSmsCodeTask, + private val finalizeAddingThreePidTask: FinalizeAddingThreePidTask, + private val deleteThreePidTask: DeleteThreePidTask, + private val pendingThreePidMapper: PendingThreePidMapper, + private val userStore: UserStore, + private val fileUploader: FileUploader +) : ProfileService { override suspend fun getDisplayName(userId: String): Optional { val params = GetProfileInfoTask.Params(userId) @@ -132,8 +134,10 @@ internal class DefaultProfileService @Inject constructor(private val taskExecuto validateSmsCodeTask.execute(ValidateSmsCodeTask.Params(threePid, code)) } - override suspend fun finalizeAddingThreePid(threePid: ThreePid, - userInteractiveAuthInterceptor: UserInteractiveAuthInterceptor) { + override suspend fun finalizeAddingThreePid( + threePid: ThreePid, + userInteractiveAuthInterceptor: UserInteractiveAuthInterceptor + ) { finalizeAddingThreePidTask .execute( FinalizeAddingThreePidTask.Params( @@ -165,8 +169,8 @@ internal class DefaultProfileService @Inject constructor(private val taskExecuto private fun UserThreePidEntity.asDomain(): ThreePid { return when (medium) { - ThirdPartyIdentifier.MEDIUM_EMAIL -> ThreePid.Email(address) + ThirdPartyIdentifier.MEDIUM_EMAIL -> ThreePid.Email(address) ThirdPartyIdentifier.MEDIUM_MSISDN -> ThreePid.Msisdn(address) - else -> error("Invalid medium type") + else -> error("Invalid medium type") } } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/profile/DeleteThreePidTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/profile/DeleteThreePidTask.kt index 7b7617aa80..a8047a0118 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/profile/DeleteThreePidTask.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/profile/DeleteThreePidTask.kt @@ -31,7 +31,8 @@ internal abstract class DeleteThreePidTask : Task -internal class DefaultRefreshUserThreePidsTask @Inject constructor(private val profileAPI: ProfileAPI, - @SessionDatabase private val monarchy: Monarchy, - private val globalErrorReceiver: GlobalErrorReceiver) : RefreshUserThreePidsTask() { +internal class DefaultRefreshUserThreePidsTask @Inject constructor( + private val profileAPI: ProfileAPI, + @SessionDatabase private val monarchy: Monarchy, + private val globalErrorReceiver: GlobalErrorReceiver +) : RefreshUserThreePidsTask() { override suspend fun execute(params: Unit) { val accountThreePidsResponse = executeRequest(globalErrorReceiver) { @@ -55,9 +57,9 @@ internal class DefaultRefreshUserThreePidsTask @Inject constructor(private val p private fun Any?.toLong(): Long { return when (this) { - null -> 0L - is Long -> this + null -> 0L + is Long -> this is Double -> this.toLong() - else -> 0L + else -> 0L } } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/profile/SetAvatarUrlTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/profile/SetAvatarUrlTask.kt index a7d116d919..80282a894b 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/profile/SetAvatarUrlTask.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/profile/SetAvatarUrlTask.kt @@ -30,7 +30,8 @@ internal abstract class SetAvatarUrlTask : Task { internal class DefaultSetAvatarUrlTask @Inject constructor( private val profileAPI: ProfileAPI, - private val globalErrorReceiver: GlobalErrorReceiver) : SetAvatarUrlTask() { + private val globalErrorReceiver: GlobalErrorReceiver +) : SetAvatarUrlTask() { override suspend fun execute(params: Params) { val body = SetAvatarUrlBody( diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/profile/SetDisplayNameTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/profile/SetDisplayNameTask.kt index 61d3042310..e3f04792b6 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/profile/SetDisplayNameTask.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/profile/SetDisplayNameTask.kt @@ -30,7 +30,8 @@ internal abstract class SetDisplayNameTask : Task Result.retry() - else -> Result.failure() + else -> Result.failure() } } } 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 67fba390d0..c2310f4fda 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,6 +15,7 @@ */ package org.matrix.android.sdk.internal.session.pushers +import org.matrix.android.sdk.api.query.QueryStringValue import org.matrix.android.sdk.api.session.events.model.Event import org.matrix.android.sdk.api.session.events.model.EventType import org.matrix.android.sdk.api.session.events.model.toModel @@ -34,22 +35,28 @@ internal class DefaultConditionResolver @Inject constructor( @UserId private val userId: String ) : ConditionResolver { - override fun resolveEventMatchCondition(event: Event, - condition: EventMatchCondition): Boolean { + override fun resolveEventMatchCondition( + event: Event, + condition: EventMatchCondition + ): Boolean { return condition.isSatisfied(event) } - override fun resolveRoomMemberCountCondition(event: Event, - condition: RoomMemberCountCondition): Boolean { + override fun resolveRoomMemberCountCondition( + event: Event, + condition: RoomMemberCountCondition + ): Boolean { return condition.isSatisfied(event, roomGetter) } - override fun resolveSenderNotificationPermissionCondition(event: Event, - condition: SenderNotificationPermissionCondition): Boolean { + override fun resolveSenderNotificationPermissionCondition( + event: Event, + condition: SenderNotificationPermissionCondition + ): Boolean { val roomId = event.roomId ?: return false val room = roomGetter.getRoom(roomId) ?: return false - val powerLevelsContent = room.getStateEvent(EventType.STATE_ROOM_POWER_LEVELS) + val powerLevelsContent = room.getStateEvent(EventType.STATE_ROOM_POWER_LEVELS, QueryStringValue.IsEmpty) ?.content ?.toModel() ?: PowerLevelsContent() @@ -57,8 +64,10 @@ internal class DefaultConditionResolver @Inject constructor( return condition.isSatisfied(event, powerLevelsContent) } - override fun resolveContainsDisplayNameCondition(event: Event, - condition: ContainsDisplayNameCondition): Boolean { + override fun resolveContainsDisplayNameCondition( + event: Event, + condition: ContainsDisplayNameCondition + ): Boolean { val roomId = event.roomId ?: return false val room = roomGetter.getRoom(roomId) ?: return false val myDisplayName = room.membershipService().getRoomMember(userId)?.displayName ?: return false diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/pushers/DefaultPushersService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/pushers/DefaultPushersService.kt index 13b990a9ff..e912d9ccf8 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/pushers/DefaultPushersService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/pushers/DefaultPushersService.kt @@ -18,6 +18,7 @@ package org.matrix.android.sdk.internal.session.pushers import androidx.lifecycle.LiveData import androidx.work.BackoffPolicy import com.zhuinden.monarchy.Monarchy +import org.matrix.android.sdk.api.session.pushers.HttpPusher import org.matrix.android.sdk.api.session.pushers.Pusher import org.matrix.android.sdk.api.session.pushers.PushersService import org.matrix.android.sdk.internal.database.mapper.asDomain @@ -45,10 +46,12 @@ internal class DefaultPushersService @Inject constructor( private val taskExecutor: TaskExecutor ) : PushersService { - override suspend fun testPush(url: String, - appId: String, - pushkey: String, - eventId: String) { + override suspend fun testPush( + url: String, + appId: String, + pushkey: String, + eventId: String + ) { pushGatewayNotifyTask.execute(PushGatewayNotifyTask.Params(url, appId, pushkey, eventId)) } @@ -58,15 +61,15 @@ internal class DefaultPushersService @Inject constructor( .executeBy(taskExecutor) } - override fun enqueueAddHttpPusher(httpPusher: PushersService.HttpPusher): UUID { + override fun enqueueAddHttpPusher(httpPusher: HttpPusher): UUID { return enqueueAddPusher(httpPusher.toJsonPusher()) } - override suspend fun addHttpPusher(httpPusher: PushersService.HttpPusher) { + override suspend fun addHttpPusher(httpPusher: HttpPusher) { addPusherTask.execute(AddPusherTask.Params(httpPusher.toJsonPusher())) } - private fun PushersService.HttpPusher.toJsonPusher() = JsonPusher( + private fun HttpPusher.toJsonPusher() = JsonPusher( pushKey = pushkey, kind = "http", appId = appId, @@ -78,12 +81,14 @@ internal class DefaultPushersService @Inject constructor( append = append ) - override suspend fun addEmailPusher(email: String, - lang: String, - emailBranding: String, - appDisplayName: String, - deviceDisplayName: String, - append: Boolean) { + override suspend fun addEmailPusher( + email: String, + lang: String, + emailBranding: String, + appDisplayName: String, + deviceDisplayName: String, + append: Boolean + ) { addPusherTask.execute( AddPusherTask.Params( JsonPusher( diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/pushers/PushRulesApi.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/pushers/PushRulesApi.kt index fbae04a1f1..1b316c7872 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 @@ -38,9 +38,11 @@ internal interface PushRulesApi { * @param enabledBody the new enable status */ @PUT(NetworkConstants.URI_API_PREFIX_PATH_R0 + "pushrules/global/{kind}/{ruleId}/enabled") - suspend fun updateEnableRuleStatus(@Path("kind") kind: String, - @Path("ruleId") ruleId: String, - @Body enabledBody: EnabledBody) + suspend fun updateEnableRuleStatus( + @Path("kind") kind: String, + @Path("ruleId") ruleId: String, + @Body enabledBody: EnabledBody + ) /** * Update the ruleID action. @@ -51,9 +53,11 @@ internal interface PushRulesApi { * @param actions the actions */ @PUT(NetworkConstants.URI_API_PREFIX_PATH_R0 + "pushrules/global/{kind}/{ruleId}/actions") - suspend fun updateRuleActions(@Path("kind") kind: String, - @Path("ruleId") ruleId: String, - @Body actions: Any) + suspend fun updateRuleActions( + @Path("kind") kind: String, + @Path("ruleId") ruleId: String, + @Body actions: Any + ) /** * Delete a rule. @@ -62,8 +66,10 @@ internal interface PushRulesApi { * @param ruleId the ruleId */ @DELETE(NetworkConstants.URI_API_PREFIX_PATH_R0 + "pushrules/global/{kind}/{ruleId}") - suspend fun deleteRule(@Path("kind") kind: String, - @Path("ruleId") ruleId: String) + suspend fun deleteRule( + @Path("kind") kind: String, + @Path("ruleId") ruleId: String + ) /** * Add the ruleID enable status. @@ -73,7 +79,9 @@ internal interface PushRulesApi { * @param rule the rule to add. */ @PUT(NetworkConstants.URI_API_PREFIX_PATH_R0 + "pushrules/global/{kind}/{ruleId}") - suspend fun addRule(@Path("kind") kind: String, - @Path("ruleId") ruleId: String, - @Body rule: PushRule) + suspend fun addRule( + @Path("kind") kind: String, + @Path("ruleId") ruleId: String, + @Body rule: PushRule + ) } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/pushers/RemovePusherTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/pushers/RemovePusherTask.kt index 057c309078..86ba33cb98 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/pushers/RemovePusherTask.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/pushers/RemovePusherTask.kt @@ -30,8 +30,10 @@ import org.matrix.android.sdk.internal.util.awaitTransaction import javax.inject.Inject internal interface RemovePusherTask : Task { - data class Params(val pushKey: String, - val pushAppId: String) + data class Params( + val pushKey: String, + val pushAppId: String + ) } internal class DefaultRemovePusherTask @Inject constructor( 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 815661a1ce..a923f1932b 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 @@ -23,9 +23,11 @@ import org.matrix.android.sdk.internal.task.Task import javax.inject.Inject internal interface UpdatePushRuleEnableStatusTask : Task { - data class Params(val kind: RuleKind, - val pushRule: PushRule, - val enabled: Boolean) + data class Params( + val kind: RuleKind, + val pushRule: PushRule, + val enabled: Boolean + ) } internal class DefaultUpdatePushRuleEnableStatusTask @Inject constructor( diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/pushrules/ProcessEventForPushTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/pushrules/ProcessEventForPushTask.kt index 60c1194708..09d7d50ecb 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/pushrules/ProcessEventForPushTask.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/pushrules/ProcessEventForPushTask.kt @@ -62,7 +62,7 @@ internal class DefaultProcessEventForPushTask @Inject constructor( EventType.REDACTION, EventType.ENCRYPTED, EventType.STATE_ROOM_MEMBER -> true - else -> false + else -> false } }.filter { it.senderId != userId 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 7326adee4c..abea2d34cd 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 @@ -23,6 +23,7 @@ import org.matrix.android.sdk.api.session.room.accountdata.RoomAccountDataServic 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.location.LocationSharingService 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 @@ -69,6 +70,7 @@ internal class DefaultRoom( private val roomAccountDataService: RoomAccountDataService, private val roomVersionService: RoomVersionService, private val viaParameterFinder: ViaParameterFinder, + private val locationSharingService: LocationSharingService, override val coroutineDispatchers: MatrixCoroutineDispatchers ) : Room { @@ -104,4 +106,5 @@ internal class DefaultRoom( override fun roomPushRuleService() = roomPushRuleService override fun roomAccountDataService() = roomAccountDataService override fun roomVersionService() = roomVersionService + override fun locationSharingService() = locationSharingService } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/DefaultRoomDirectoryService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/DefaultRoomDirectoryService.kt index 7330c91c20..1edc4c1e66 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/DefaultRoomDirectoryService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/DefaultRoomDirectoryService.kt @@ -35,8 +35,10 @@ internal class DefaultRoomDirectoryService @Inject constructor( private val roomAliasAvailabilityChecker: RoomAliasAvailabilityChecker ) : RoomDirectoryService { - override suspend fun getPublicRooms(server: String?, - publicRoomsParams: PublicRoomsParams): PublicRoomsResponse { + override suspend fun getPublicRooms( + server: String?, + publicRoomsParams: PublicRoomsParams + ): PublicRoomsResponse { return getPublicRoomTask.execute(GetPublicRoomTask.Params(server, publicRoomsParams)) } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/DefaultRoomService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/DefaultRoomService.kt index 8424ee8a36..5e6d052443 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/DefaultRoomService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/DefaultRoomService.kt @@ -20,7 +20,6 @@ import androidx.lifecycle.LiveData import androidx.lifecycle.Transformations import androidx.paging.PagedList import com.zhuinden.monarchy.Monarchy -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.identity.model.SignInvitationResult import org.matrix.android.sdk.api.session.room.Room @@ -91,18 +90,27 @@ internal class DefaultRoomService @Inject constructor( return roomSummaryDataSource.getRoomSummary(roomIdOrAlias) } - override fun getRoomSummaries(queryParams: RoomSummaryQueryParams, - sortOrder: RoomSortOrder): List { + override fun getRoomSummaryLive(roomId: String): LiveData> { + return roomSummaryDataSource.getRoomSummaryLive(roomId) + } + + override fun getRoomSummaries( + queryParams: RoomSummaryQueryParams, + sortOrder: RoomSortOrder + ): List { return roomSummaryDataSource.getRoomSummaries(queryParams, sortOrder) } override fun refreshJoinedRoomSummaryPreviews(roomId: String?) { - val roomSummaries = getRoomSummaries(roomSummaryQueryParams { - if (roomId != null) { - this.roomId = QueryStringValue.Equals(roomId) - } - memberships = listOf(Membership.JOIN) - }) + val roomSummaries = if (roomId == null) { + getRoomSummaries(roomSummaryQueryParams { + memberships = listOf(Membership.JOIN) + }) + } else { + listOfNotNull( + getRoomSummary(roomId)?.takeIf { it.membership == Membership.JOIN } + ) + } if (roomSummaries.isNotEmpty()) { monarchy.runTransactionSync { realm -> @@ -113,21 +121,28 @@ internal class DefaultRoomService @Inject constructor( } } - override fun getRoomSummariesLive(queryParams: RoomSummaryQueryParams, - sortOrder: RoomSortOrder): LiveData> { + override fun getRoomSummariesLive( + queryParams: RoomSummaryQueryParams, + sortOrder: RoomSortOrder + ): LiveData> { return roomSummaryDataSource.getRoomSummariesLive(queryParams, sortOrder) } - override fun getPagedRoomSummariesLive(queryParams: RoomSummaryQueryParams, - pagedListConfig: PagedList.Config, - sortOrder: RoomSortOrder): LiveData> { + override fun getPagedRoomSummariesLive( + queryParams: RoomSummaryQueryParams, + pagedListConfig: PagedList.Config, + sortOrder: RoomSortOrder + ): LiveData> { return roomSummaryDataSource.getSortedPagedRoomSummariesLive(queryParams, pagedListConfig, sortOrder) } - override fun getFilteredPagedRoomSummariesLive(queryParams: RoomSummaryQueryParams, - pagedListConfig: PagedList.Config, - sortOrder: RoomSortOrder): UpdatableLivePageResult { - return roomSummaryDataSource.getUpdatablePagedRoomSummariesLive(queryParams, pagedListConfig, sortOrder) + override fun getFilteredPagedRoomSummariesLive( + queryParams: RoomSummaryQueryParams, + pagedListConfig: PagedList.Config, + sortOrder: RoomSortOrder, + getFlattenParents: Boolean + ): UpdatableLivePageResult { + return roomSummaryDataSource.getUpdatablePagedRoomSummariesLive(queryParams, pagedListConfig, sortOrder, getFlattenParents) } override fun getRoomCountLive(queryParams: RoomSummaryQueryParams): LiveData { @@ -154,9 +169,11 @@ internal class DefaultRoomService @Inject constructor( joinRoomTask.execute(JoinRoomTask.Params(roomIdOrAlias, reason, viaServers)) } - override suspend fun joinRoom(roomId: String, - reason: String?, - thirdPartySigned: SignInvitationResult) { + override suspend fun joinRoom( + roomId: String, + reason: String?, + thirdPartySigned: SignInvitationResult + ) { joinRoomTask.execute(JoinRoomTask.Params(roomId, reason, thirdPartySigned = thirdPartySigned)) } 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 3efeef7688..24d4975eb9 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 @@ -103,12 +103,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) @@ -134,7 +134,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) { @@ -143,7 +143,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 || @@ -168,20 +168,20 @@ 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}") encryptedEventContent.relatesTo.eventId?.let { handleVerification(realm, event, roomId, isLocalEcho, it) } } - in EventType.POLL_RESPONSE -> { + in EventType.POLL_RESPONSE -> { event.getClearContent().toModel(catchError = true)?.let { sessionManager.getSessionComponent(sessionId)?.session()?.let { session -> pollAggregationProcessor.handlePollResponseEvent(session, realm, event) } } } - in EventType.POLL_END -> { + in EventType.POLL_END -> { sessionManager.getSessionComponent(sessionId)?.session()?.let { session -> getPowerLevelsHelper(event.roomId)?.let { pollAggregationProcessor.handlePollEndEvent(session, it, realm, event) @@ -211,11 +211,11 @@ internal class EventRelationsAggregationProcessor @Inject constructor( // } // } } - EventType.REDACTION -> { + EventType.REDACTION -> { val eventToPrune = event.redacts?.let { EventEntity.where(realm, eventId = it).findFirst() } ?: return when (eventToPrune.type) { - EventType.MESSAGE -> { + EventType.MESSAGE -> { Timber.d("REDACTION for message ${eventToPrune.eventId}") // val unsignedData = EventMapper.map(eventToPrune).unsignedData // ?: UnsignedData(null, null) @@ -231,7 +231,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}") @@ -239,14 +239,14 @@ 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 { sessionManager.getSessionComponent(sessionId)?.session()?.let { session -> pollAggregationProcessor.handlePollResponseEvent(session, realm, event) } } } - in EventType.POLL_END -> { + in EventType.POLL_END -> { sessionManager.getSessionComponent(sessionId)?.session()?.let { session -> getPowerLevelsHelper(event.roomId)?.let { pollAggregationProcessor.handlePollEndEvent(session, it, realm, event) @@ -261,7 +261,7 @@ internal class EventRelationsAggregationProcessor @Inject constructor( in EventType.BEACON_LOCATION_DATA -> { handleBeaconLocationData(event, realm, 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 ") @@ -271,12 +271,14 @@ internal class EventRelationsAggregationProcessor @Inject constructor( // OPT OUT serer aggregation until API mature enough private val SHOULD_HANDLE_SERVER_AGREGGATION = false // should be true to work with e2e - private fun handleReplace(realm: Realm, - event: Event, - content: MessageContent, - roomId: String, - isLocalEcho: Boolean, - relatedEventId: String? = null) { + private fun handleReplace( + realm: Realm, + event: Event, + content: MessageContent, + roomId: String, + isLocalEcho: Boolean, + relatedEventId: String? = null + ) { val eventId = event.eventId ?: return val targetEventId = relatedEventId ?: content.relatesTo?.eventId ?: return val newContent = content.newContent ?: return @@ -366,9 +368,11 @@ internal class EventRelationsAggregationProcessor @Inject constructor( * @param replaceEvent The new event * @param editions list of edition of event */ - private fun handleThreadSummaryEdition(editedEvent: EventEntity?, - replaceEvent: TimelineEventEntity?, - editions: List?) { + private fun handleThreadSummaryEdition( + editedEvent: EventEntity?, + replaceEvent: TimelineEventEntity?, + editions: List? + ) { replaceEvent ?: return editedEvent ?: return editedEvent.findRootThreadEvent()?.apply { @@ -382,15 +386,17 @@ internal class EventRelationsAggregationProcessor @Inject constructor( } private fun getPowerLevelsHelper(roomId: String): PowerLevelsHelper? { - return stateEventDataSource.getStateEvent(roomId, EventType.STATE_ROOM_POWER_LEVELS, QueryStringValue.NoCondition) + return stateEventDataSource.getStateEvent(roomId, EventType.STATE_ROOM_POWER_LEVELS, QueryStringValue.IsEmpty) ?.content?.toModel() ?.let { PowerLevelsHelper(it) } } - private fun handleInitialAggregatedRelations(realm: Realm, - event: Event, - roomId: String, - aggregation: AggregatedAnnotation) { + private fun handleInitialAggregatedRelations( + realm: Realm, + event: Event, + roomId: String, + aggregation: AggregatedAnnotation + ) { if (SHOULD_HANDLE_SERVER_AGREGGATION) { aggregation.chunk?.forEach { if (it.type == EventType.REACTION) { @@ -412,10 +418,12 @@ internal class EventRelationsAggregationProcessor @Inject constructor( } } - private fun handleReaction(realm: Realm, - event: Event, - roomId: String, - isLocalEcho: Boolean) { + private fun handleReaction( + realm: Realm, + event: Event, + roomId: String, + isLocalEcho: Boolean + ) { val content = event.content.toModel() if (content == null) { Timber.e("Malformed reaction content ${event.content}") @@ -480,9 +488,11 @@ internal class EventRelationsAggregationProcessor @Inject constructor( /** * Called when an event is deleted. */ - private fun handleRedactionOfReplace(realm: Realm, - redacted: EventEntity, - relatedEventId: String) { + private fun handleRedactionOfReplace( + realm: Realm, + redacted: EventEntity, + relatedEventId: String + ) { Timber.d("Handle redaction of m.replace") val eventSummary = EventAnnotationsSummaryEntity.where(realm, redacted.roomId, relatedEventId).findFirst() if (eventSummary == null) { @@ -498,8 +508,10 @@ internal class EventRelationsAggregationProcessor @Inject constructor( sourceToDiscard.deleteFromRealm() } - private fun handleReactionRedact(realm: Realm, - eventToPrune: EventEntity) { + private fun handleReactionRedact( + realm: Realm, + eventToPrune: EventEntity + ) { Timber.v("REDACTION of reaction ${eventToPrune.eventId}") // delete a reaction, need to update the annotation summary if any val reactionContent: ReactionContent = EventMapper.map(eventToPrune).content.toModel() ?: return @@ -560,7 +572,7 @@ internal class EventRelationsAggregationProcessor @Inject constructor( EventType.KEY_VERIFICATION_ACCEPT, EventType.KEY_VERIFICATION_READY, EventType.KEY_VERIFICATION_KEY, - EventType.KEY_VERIFICATION_MAC -> currentState.toState(VerificationState.WAITING) + EventType.KEY_VERIFICATION_MAC -> currentState.toState(VerificationState.WAITING) EventType.KEY_VERIFICATION_CANCEL -> currentState.toState( if (event.senderId == userId) { VerificationState.CANCELED_BY_ME @@ -568,8 +580,8 @@ internal class EventRelationsAggregationProcessor @Inject constructor( VerificationState.CANCELED_BY_OTHER } ) - EventType.KEY_VERIFICATION_DONE -> currentState.toState(VerificationState.DONE) - else -> VerificationState.REQUEST + EventType.KEY_VERIFICATION_DONE -> currentState.toState(VerificationState.DONE) + else -> VerificationState.REQUEST } data = data.copy(verificationState = newState) 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 ba7f4cf5ad..ac2880de69 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 @@ -58,8 +58,9 @@ internal interface RoomAPI { * Ref: https://matrix.org/docs/spec/client_server/r0.4.0.html#post-matrix-client-r0-publicrooms */ @POST(NetworkConstants.URI_API_PREFIX_PATH_R0 + "publicRooms") - suspend fun publicRooms(@Query("server") server: String?, - @Body publicRoomsParams: PublicRoomsParams + suspend fun publicRooms( + @Query("server") server: String?, + @Body publicRoomsParams: PublicRoomsParams ): PublicRoomsResponse /** @@ -84,11 +85,12 @@ internal interface RoomAPI { * @param filter A JSON RoomEventFilter to filter returned events with. Optional. */ @GET(NetworkConstants.URI_API_PREFIX_PATH_R0 + "rooms/{roomId}/messages") - suspend fun getRoomMessagesFrom(@Path("roomId") roomId: String, - @Query("from") from: String, - @Query("dir") dir: String, - @Query("limit") limit: Int?, - @Query("filter") filter: String? + suspend fun getRoomMessagesFrom( + @Path("roomId") roomId: String, + @Query("from") from: String, + @Query("dir") dir: String, + @Query("limit") limit: Int?, + @Query("filter") filter: String? ): PaginationResponse /** @@ -100,10 +102,11 @@ internal interface RoomAPI { * @param notMembership to exclude one type of membership (optional) */ @GET(NetworkConstants.URI_API_PREFIX_PATH_R0 + "rooms/{roomId}/members") - suspend fun getMembers(@Path("roomId") roomId: String, - @Query("at") syncToken: String?, - @Query("membership") membership: Membership?, - @Query("not_membership") notMembership: Membership? + suspend fun getMembers( + @Path("roomId") roomId: String, + @Query("at") syncToken: String?, + @Query("membership") membership: Membership?, + @Query("not_membership") notMembership: Membership? ): RoomMembersResponse /** @@ -115,10 +118,11 @@ internal interface RoomAPI { * @param content the event content */ @PUT(NetworkConstants.URI_API_PREFIX_PATH_R0 + "rooms/{roomId}/send/{eventType}/{txId}") - suspend fun send(@Path("txId") txId: String, - @Path("roomId") roomId: String, - @Path("eventType") eventType: String, - @Body content: Content? + suspend fun send( + @Path("txId") txId: String, + @Path("roomId") roomId: String, + @Path("eventType") eventType: String, + @Body content: Content? ): SendResponse /** @@ -130,10 +134,12 @@ internal interface RoomAPI { * @param filter A JSON RoomEventFilter to filter returned events with. Optional. */ @GET(NetworkConstants.URI_API_PREFIX_PATH_R0 + "rooms/{roomId}/context/{eventId}") - suspend fun getContextOfEvent(@Path("roomId") roomId: String, - @Path("eventId") eventId: String, - @Query("limit") limit: Int, - @Query("filter") filter: String? = null): EventContextResponse + suspend fun getContextOfEvent( + @Path("roomId") roomId: String, + @Path("eventId") eventId: String, + @Query("limit") limit: Int, + @Query("filter") filter: String? = null + ): EventContextResponse /** * Retrieve an event from its room id / events id. @@ -142,8 +148,10 @@ internal interface RoomAPI { * @param eventId the event Id */ @GET(NetworkConstants.URI_API_PREFIX_PATH_R0 + "rooms/{roomId}/event/{eventId}") - suspend fun getEvent(@Path("roomId") roomId: String, - @Path("eventId") eventId: String): Event + suspend fun getEvent( + @Path("roomId") roomId: String, + @Path("eventId") eventId: String + ): Event /** * Send read markers. @@ -152,17 +160,21 @@ internal interface RoomAPI { * @param markers the read markers */ @POST(NetworkConstants.URI_API_PREFIX_PATH_R0 + "rooms/{roomId}/read_markers") - suspend fun sendReadMarker(@Path("roomId") roomId: String, - @Body markers: Map) + suspend fun sendReadMarker( + @Path("roomId") roomId: String, + @Body markers: Map + ) /** * Send receipt to a room. */ @POST(NetworkConstants.URI_API_PREFIX_PATH_R0 + "rooms/{roomId}/receipt/{receiptType}/{eventId}") - suspend fun sendReceipt(@Path("roomId") roomId: String, - @Path("receiptType") receiptType: String, - @Path("eventId") eventId: String, - @Body body: JsonDict = emptyMap()) + suspend fun sendReceipt( + @Path("roomId") roomId: String, + @Path("receiptType") receiptType: String, + @Path("eventId") eventId: String, + @Body body: JsonDict = emptyMap() + ) /** * Invite a user to the given room. @@ -172,8 +184,10 @@ internal interface RoomAPI { * @param body a object that just contains a user id */ @POST(NetworkConstants.URI_API_PREFIX_PATH_R0 + "rooms/{roomId}/invite") - suspend fun invite(@Path("roomId") roomId: String, - @Body body: InviteBody) + suspend fun invite( + @Path("roomId") roomId: String, + @Body body: InviteBody + ) /** * Invite a user to a room, using a ThreePid @@ -182,8 +196,10 @@ internal interface RoomAPI { * @param body the Json body */ @POST(NetworkConstants.URI_API_PREFIX_PATH_R0 + "rooms/{roomId}/invite") - suspend fun invite3pid(@Path("roomId") roomId: String, - @Body body: ThreePidInviteBody) + suspend fun invite3pid( + @Path("roomId") roomId: String, + @Body body: ThreePidInviteBody + ) /** * Send a generic state event. @@ -193,9 +209,10 @@ internal interface RoomAPI { * @param params the request parameters */ @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 + suspend fun sendStateEvent( + @Path("roomId") roomId: String, + @Path("state_event_type") stateEventType: String, + @Body params: JsonDict ): SendResponse /** @@ -207,10 +224,11 @@ internal interface RoomAPI { * @param params the request parameters */ @PUT(NetworkConstants.URI_API_PREFIX_PATH_R0 + "rooms/{roomId}/state/{state_event_type}/{state_key}") - suspend fun sendStateEvent(@Path("roomId") roomId: String, - @Path("state_event_type") stateEventType: String, - @Path("state_key") stateKey: String, - @Body params: JsonDict + suspend fun sendStateEvent( + @Path("roomId") roomId: String, + @Path("state_event_type") stateEventType: String, + @Path("state_key") stateKey: String, + @Body params: JsonDict ): SendResponse /** @@ -231,13 +249,14 @@ internal interface RoomAPI { * @param limit max number of Event to retrieve */ @GET(NetworkConstants.URI_API_PREFIX_PATH_UNSTABLE + "rooms/{roomId}/relations/{eventId}/{relationType}/{eventType}") - suspend fun getRelations(@Path("roomId") roomId: String, - @Path("eventId") eventId: String, - @Path("relationType") relationType: String, - @Path("eventType") eventType: String, - @Query("from") from: String? = null, - @Query("to") to: String? = null, - @Query("limit") limit: Int? = null + suspend fun getRelations( + @Path("roomId") roomId: String, + @Path("eventId") eventId: String, + @Path("relationType") relationType: String, + @Path("eventType") eventType: String, + @Query("from") from: String? = null, + @Query("to") to: String? = null, + @Query("limit") limit: Int? = null ): RelationsResponse /** @@ -251,12 +270,13 @@ internal interface RoomAPI { * @param limit max number of Event to retrieve */ @GET(NetworkConstants.URI_API_PREFIX_PATH_UNSTABLE + "rooms/{roomId}/relations/{eventId}/{relationType}") - suspend fun getThreadsRelations(@Path("roomId") roomId: String, - @Path("eventId") eventId: String, - @Path("relationType") relationType: String = RelationType.THREAD, - @Query("from") from: String? = null, - @Query("to") to: String? = null, - @Query("limit") limit: Int? = null + suspend fun getThreadsRelations( + @Path("roomId") roomId: String, + @Path("eventId") eventId: String, + @Path("relationType") relationType: String = RelationType.THREAD, + @Query("from") from: String? = null, + @Query("to") to: String? = null, + @Query("limit") limit: Int? = null ): RelationsResponse /** @@ -267,9 +287,11 @@ internal interface RoomAPI { * @param params the request body */ @POST(NetworkConstants.URI_API_PREFIX_PATH_R0 + "join/{roomIdOrAlias}") - suspend fun join(@Path("roomIdOrAlias") roomIdOrAlias: String, - @Query("server_name") viaServers: List, - @Body params: JsonDict): JoinRoomResponse + suspend fun join( + @Path("roomIdOrAlias") roomIdOrAlias: String, + @Query("server_name") viaServers: List, + @Body params: JsonDict + ): JoinRoomResponse /** * Leave the given room. @@ -278,8 +300,10 @@ internal interface RoomAPI { * @param params the request body */ @POST(NetworkConstants.URI_API_PREFIX_PATH_R0 + "rooms/{roomId}/leave") - suspend fun leave(@Path("roomId") roomId: String, - @Body params: Map) + suspend fun leave( + @Path("roomId") roomId: String, + @Body params: Map + ) /** * Ban a user from the given room. @@ -288,8 +312,10 @@ internal interface RoomAPI { * @param userIdAndReason the banned user object (userId and reason for ban) */ @POST(NetworkConstants.URI_API_PREFIX_PATH_R0 + "rooms/{roomId}/ban") - suspend fun ban(@Path("roomId") roomId: String, - @Body userIdAndReason: UserIdAndReason) + suspend fun ban( + @Path("roomId") roomId: String, + @Body userIdAndReason: UserIdAndReason + ) /** * unban a user from the given room. @@ -298,8 +324,10 @@ internal interface RoomAPI { * @param userIdAndReason the unbanned user object (userId and reason for unban) */ @POST(NetworkConstants.URI_API_PREFIX_PATH_R0 + "rooms/{roomId}/unban") - suspend fun unban(@Path("roomId") roomId: String, - @Body userIdAndReason: UserIdAndReason) + suspend fun unban( + @Path("roomId") roomId: String, + @Body userIdAndReason: UserIdAndReason + ) /** * Kick a user from the given room. @@ -308,8 +336,10 @@ internal interface RoomAPI { * @param userIdAndReason the kicked user object (userId and reason for kicking) */ @POST(NetworkConstants.URI_API_PREFIX_PATH_R0 + "rooms/{roomId}/kick") - suspend fun kick(@Path("roomId") roomId: String, - @Body userIdAndReason: UserIdAndReason) + suspend fun kick( + @Path("roomId") roomId: String, + @Body userIdAndReason: UserIdAndReason + ) /** * Strips all information out of an event which isn't critical to the integrity of the server-side representation of the room. @@ -337,9 +367,11 @@ internal interface RoomAPI { * @param body body containing score and reason */ @POST(NetworkConstants.URI_API_PREFIX_PATH_R0 + "rooms/{roomId}/report/{eventId}") - suspend fun reportContent(@Path("roomId") roomId: String, - @Path("eventId") eventId: String, - @Body body: ReportContentBody) + suspend fun reportContent( + @Path("roomId") roomId: String, + @Path("eventId") eventId: String, + @Body body: ReportContentBody + ) /** * Get a list of aliases maintained by the local server for the given room. @@ -352,9 +384,11 @@ internal interface RoomAPI { * Inform that the user is starting to type or has stopped typing. */ @PUT(NetworkConstants.URI_API_PREFIX_PATH_R0 + "rooms/{roomId}/typing/{userId}") - suspend fun sendTypingState(@Path("roomId") roomId: String, - @Path("userId") userId: String, - @Body body: TypingBody) + suspend fun sendTypingState( + @Path("roomId") roomId: String, + @Path("userId") userId: String, + @Body body: TypingBody + ) /* * Room tagging @@ -364,27 +398,33 @@ internal interface RoomAPI { * Add a tag to a room. */ @PUT(NetworkConstants.URI_API_PREFIX_PATH_R0 + "user/{userId}/rooms/{roomId}/tags/{tag}") - suspend fun putTag(@Path("userId") userId: String, - @Path("roomId") roomId: String, - @Path("tag") tag: String, - @Body body: TagBody) + suspend fun putTag( + @Path("userId") userId: String, + @Path("roomId") roomId: String, + @Path("tag") tag: String, + @Body body: TagBody + ) /** * Delete a tag from a room. */ @DELETE(NetworkConstants.URI_API_PREFIX_PATH_R0 + "user/{userId}/rooms/{roomId}/tags/{tag}") - suspend fun deleteTag(@Path("userId") userId: String, - @Path("roomId") roomId: String, - @Path("tag") tag: String) + suspend fun deleteTag( + @Path("userId") userId: String, + @Path("roomId") roomId: String, + @Path("tag") tag: String + ) /** * Set an AccountData event to the room. */ @PUT(NetworkConstants.URI_API_PREFIX_PATH_R0 + "user/{userId}/rooms/{roomId}/account_data/{type}") - suspend fun setRoomAccountData(@Path("userId") userId: String, - @Path("roomId") roomId: String, - @Path("type") type: String, - @Body content: JsonDict) + suspend fun setRoomAccountData( + @Path("userId") userId: String, + @Path("roomId") roomId: String, + @Path("type") type: String, + @Body content: JsonDict + ) /** * Upgrades the given room to a particular room version. @@ -394,8 +434,10 @@ internal interface RoomAPI { * 403: The user is not permitted to upgrade the room.(M_FORBIDDEN) */ @POST(NetworkConstants.URI_API_PREFIX_PATH_R0 + "rooms/{roomId}/upgrade") - suspend fun upgradeRoom(@Path("roomId") roomId: String, - @Body body: RoomUpgradeBody): RoomUpgradeResponse + suspend fun upgradeRoom( + @Path("roomId") roomId: String, + @Body body: RoomUpgradeBody + ): RoomUpgradeResponse /** * The API returns the summary of the specified room, if the room could be found and the client should be able to view @@ -404,6 +446,8 @@ internal interface RoomAPI { * https://github.com/deepbluev7/matrix-doc/blob/room-summaries/proposals/3266-room-summary.md */ @GET(NetworkConstants.URI_API_PREFIX_PATH_UNSTABLE + "im.nheko.summary/rooms/{roomIdOrAlias}/summary") - suspend fun getRoomSummary(@Path("roomIdOrAlias") roomidOrAlias: String, - @Query("via") viaServers: List?): RoomStrippedState + suspend fun getRoomSummary( + @Path("roomIdOrAlias") roomidOrAlias: String, + @Query("via") viaServers: List? + ): RoomStrippedState } 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 01c4fd1501..ffe7679575 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 @@ -25,6 +25,7 @@ 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.location.DefaultLocationSharingService import org.matrix.android.sdk.internal.session.room.membership.DefaultMembershipService import org.matrix.android.sdk.internal.session.room.notification.DefaultRoomPushRuleService import org.matrix.android.sdk.internal.session.room.read.DefaultReadService @@ -69,6 +70,7 @@ internal class DefaultRoomFactory @Inject constructor( private val roomVersionServiceFactory: DefaultRoomVersionService.Factory, private val roomAccountDataServiceFactory: DefaultRoomAccountDataService.Factory, private val viaParameterFinder: ViaParameterFinder, + private val locationSharingServiceFactory: DefaultLocationSharingService.Factory, private val coroutineDispatchers: MatrixCoroutineDispatchers ) : RoomFactory { @@ -96,6 +98,7 @@ internal class DefaultRoomFactory @Inject constructor( roomAccountDataService = roomAccountDataServiceFactory.create(roomId), roomVersionService = roomVersionServiceFactory.create(roomId), viaParameterFinder = viaParameterFinder, + locationSharingService = locationSharingServiceFactory.create(roomId), coroutineDispatchers = coroutineDispatchers ) } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/RoomModule.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/RoomModule.kt index f3845f1f15..c4d37d124b 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/RoomModule.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/RoomModule.kt @@ -51,6 +51,18 @@ import org.matrix.android.sdk.internal.session.room.directory.DefaultSetRoomDire import org.matrix.android.sdk.internal.session.room.directory.GetPublicRoomTask import org.matrix.android.sdk.internal.session.room.directory.GetRoomDirectoryVisibilityTask import org.matrix.android.sdk.internal.session.room.directory.SetRoomDirectoryVisibilityTask +import org.matrix.android.sdk.internal.session.room.location.CheckIfExistingActiveLiveTask +import org.matrix.android.sdk.internal.session.room.location.DefaultCheckIfExistingActiveLiveTask +import org.matrix.android.sdk.internal.session.room.location.DefaultGetActiveBeaconInfoForUserTask +import org.matrix.android.sdk.internal.session.room.location.DefaultSendLiveLocationTask +import org.matrix.android.sdk.internal.session.room.location.DefaultSendStaticLocationTask +import org.matrix.android.sdk.internal.session.room.location.DefaultStartLiveLocationShareTask +import org.matrix.android.sdk.internal.session.room.location.DefaultStopLiveLocationShareTask +import org.matrix.android.sdk.internal.session.room.location.GetActiveBeaconInfoForUserTask +import org.matrix.android.sdk.internal.session.room.location.SendLiveLocationTask +import org.matrix.android.sdk.internal.session.room.location.SendStaticLocationTask +import org.matrix.android.sdk.internal.session.room.location.StartLiveLocationShareTask +import org.matrix.android.sdk.internal.session.room.location.StopLiveLocationShareTask import org.matrix.android.sdk.internal.session.room.membership.DefaultLoadRoomMembersTask import org.matrix.android.sdk.internal.session.room.membership.LoadRoomMembersTask import org.matrix.android.sdk.internal.session.room.membership.admin.DefaultMembershipAdminTask @@ -299,4 +311,22 @@ internal abstract class RoomModule { @Binds abstract fun bindFetchThreadSummariesTask(task: DefaultFetchThreadSummariesTask): FetchThreadSummariesTask + + @Binds + abstract fun bindStartLiveLocationShareTask(task: DefaultStartLiveLocationShareTask): StartLiveLocationShareTask + + @Binds + abstract fun bindStopLiveLocationShareTask(task: DefaultStopLiveLocationShareTask): StopLiveLocationShareTask + + @Binds + abstract fun bindSendStaticLocationTask(task: DefaultSendStaticLocationTask): SendStaticLocationTask + + @Binds + abstract fun bindSendLiveLocationTask(task: DefaultSendLiveLocationTask): SendLiveLocationTask + + @Binds + abstract fun bindGetActiveBeaconInfoForUserTask(task: DefaultGetActiveBeaconInfoForUserTask): GetActiveBeaconInfoForUserTask + + @Binds + abstract fun bindCheckIfExistingActiveLiveTask(task: DefaultCheckIfExistingActiveLiveTask): CheckIfExistingActiveLiveTask } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/accountdata/DefaultRoomAccountDataService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/accountdata/DefaultRoomAccountDataService.kt index caeeb3bf53..aea786b46b 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/accountdata/DefaultRoomAccountDataService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/accountdata/DefaultRoomAccountDataService.kt @@ -25,9 +25,10 @@ import org.matrix.android.sdk.api.session.room.accountdata.RoomAccountDataEvent import org.matrix.android.sdk.api.session.room.accountdata.RoomAccountDataService import org.matrix.android.sdk.api.util.Optional -internal class DefaultRoomAccountDataService @AssistedInject constructor(@Assisted private val roomId: String, - private val dataSource: RoomAccountDataDataSource, - private val updateRoomAccountDataTask: UpdateRoomAccountDataTask +internal class DefaultRoomAccountDataService @AssistedInject constructor( + @Assisted private val roomId: String, + private val dataSource: RoomAccountDataDataSource, + private val updateRoomAccountDataTask: UpdateRoomAccountDataTask ) : RoomAccountDataService { @AssistedFactory diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/accountdata/RoomAccountDataDataSource.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/accountdata/RoomAccountDataDataSource.kt index d5a110dfc2..5196f004ea 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/accountdata/RoomAccountDataDataSource.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/accountdata/RoomAccountDataDataSource.kt @@ -33,9 +33,11 @@ import org.matrix.android.sdk.internal.database.model.RoomEntityFields import org.matrix.android.sdk.internal.di.SessionDatabase import javax.inject.Inject -internal class RoomAccountDataDataSource @Inject constructor(@SessionDatabase private val monarchy: Monarchy, - private val realmSessionProvider: RealmSessionProvider, - private val accountDataMapper: AccountDataMapper) { +internal class RoomAccountDataDataSource @Inject constructor( + @SessionDatabase private val monarchy: Monarchy, + private val realmSessionProvider: RealmSessionProvider, + private val accountDataMapper: AccountDataMapper +) { fun getAccountDataEvent(roomId: String, type: String): RoomAccountDataEvent? { return getAccountDataEvents(roomId, setOf(type)).firstOrNull() diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/aggregation/livelocation/DeactivateLiveLocationShareWorker.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/aggregation/livelocation/DeactivateLiveLocationShareWorker.kt new file mode 100644 index 0000000000..b17753b25a --- /dev/null +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/aggregation/livelocation/DeactivateLiveLocationShareWorker.kt @@ -0,0 +1,98 @@ +/* + * 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.aggregation.livelocation + +import android.content.Context +import androidx.work.WorkerParameters +import com.squareup.moshi.JsonClass +import io.realm.RealmConfiguration +import org.matrix.android.sdk.api.util.md5 +import org.matrix.android.sdk.internal.SessionManager +import org.matrix.android.sdk.internal.database.awaitTransaction +import org.matrix.android.sdk.internal.database.model.livelocation.LiveLocationShareAggregatedSummaryEntity +import org.matrix.android.sdk.internal.database.query.get +import org.matrix.android.sdk.internal.di.SessionDatabase +import org.matrix.android.sdk.internal.session.SessionComponent +import org.matrix.android.sdk.internal.worker.SessionSafeCoroutineWorker +import org.matrix.android.sdk.internal.worker.SessionWorkerParams +import timber.log.Timber +import javax.inject.Inject + +/** + * Worker dedicated to update live location summary data so that it is considered as deactivated. + * For the context: it is needed since a live location share should be deactivated after a certain timeout. + */ +internal class DeactivateLiveLocationShareWorker(context: Context, params: WorkerParameters, sessionManager: SessionManager) : + SessionSafeCoroutineWorker( + context, + params, + sessionManager, + Params::class.java + ) { + + @JsonClass(generateAdapter = true) + internal data class Params( + override val sessionId: String, + override val lastFailureMessage: String? = null, + val eventId: String, + val roomId: String + ) : SessionWorkerParams + + @SessionDatabase + @Inject lateinit var realmConfiguration: RealmConfiguration + + override fun injectWith(injector: SessionComponent) { + injector.inject(this) + } + + override suspend fun doSafeWork(params: Params): Result { + return runCatching { + deactivateLiveLocationShare(params) + }.fold( + onSuccess = { + Result.success() + }, + onFailure = { + Timber.e("failed to deactivate live, eventId: ${params.eventId}, roomId: ${params.roomId}") + Result.failure() + } + ) + } + + private suspend fun deactivateLiveLocationShare(params: Params) { + awaitTransaction(realmConfiguration) { realm -> + Timber.d("deactivating live with id=${params.eventId}") + val aggregatedSummary = LiveLocationShareAggregatedSummaryEntity.get( + realm = realm, + roomId = params.roomId, + eventId = params.eventId + ) + aggregatedSummary?.isActive = false + } + } + + override fun buildErrorParams(params: Params, message: String): Params { + return params.copy(lastFailureMessage = params.lastFailureMessage ?: message) + } + + companion object { + fun getWorkName(eventId: String, roomId: String): String { + val hash = "$eventId$roomId".md5() + return "DeactivateLiveLocationWork-$hash" + } + } +} 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 76b7a4ec8e..921749122b 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 @@ -16,6 +16,7 @@ package org.matrix.android.sdk.internal.session.room.aggregation.livelocation +import androidx.work.ExistingWorkPolicy import io.realm.Realm import org.matrix.android.sdk.api.extensions.orTrue import org.matrix.android.sdk.api.session.events.model.Event @@ -25,18 +26,36 @@ import org.matrix.android.sdk.api.session.room.model.message.MessageBeaconInfoCo 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.livelocation.LiveLocationShareAggregatedSummaryEntity +import org.matrix.android.sdk.internal.database.query.findActiveLiveInRoomForUser import org.matrix.android.sdk.internal.database.query.getOrCreate +import org.matrix.android.sdk.internal.di.SessionId +import org.matrix.android.sdk.internal.di.WorkManagerProvider +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.TimeUnit import javax.inject.Inject -internal class LiveLocationAggregationProcessor @Inject constructor() { +/** + * Aggregates all live location sharing related events in local database. + */ +internal class LiveLocationAggregationProcessor @Inject constructor( + @SessionId private val sessionId: String, + private val workManagerProvider: WorkManagerProvider, + private val clock: Clock, +) { - fun handleBeaconInfo(realm: Realm, event: Event, content: MessageBeaconInfoContent, roomId: String, isLocalEcho: Boolean) { + /** + * Handle the content of a beacon info. + * @return true if it has been processed, false if ignored. + */ + fun handleBeaconInfo(realm: Realm, event: Event, content: MessageBeaconInfoContent, roomId: String, isLocalEcho: Boolean): Boolean { if (event.senderId.isNullOrEmpty() || isLocalEcho) { - return + return false } - val targetEventId = if (content.isLive.orTrue()) { + val isLive = content.isLive.orTrue() + val targetEventId = if (isLive) { event.eventId } else { // when live is set to false, we use the id of the event that should have been replaced @@ -45,7 +64,7 @@ internal class LiveLocationAggregationProcessor @Inject constructor() { if (targetEventId.isNullOrEmpty()) { Timber.w("no target event id found for the beacon content") - return + return false } val aggregatedSummary = LiveLocationShareAggregatedSummaryEntity.getOrCreate( @@ -54,12 +73,55 @@ internal class LiveLocationAggregationProcessor @Inject constructor() { eventId = targetEventId ) - Timber.d("updating summary of id=$targetEventId with isLive=${content.isLive}") + // remote event can stay with isLive == true while the local summary is no more active + val isActive = aggregatedSummary.isActive.orTrue() && isLive + val endOfLiveTimestampMillis = content.getBestTimestampMillis()?.let { it + (content.timeout ?: 0) } + Timber.d("updating summary of id=$targetEventId with isActive=$isActive and endTimestamp=$endOfLiveTimestampMillis") - aggregatedSummary.endOfLiveTimestampMillis = content.getBestTimestampMillis()?.let { it + (content.timeout ?: 0) } - aggregatedSummary.isActive = content.isLive + aggregatedSummary.endOfLiveTimestampMillis = endOfLiveTimestampMillis + aggregatedSummary.isActive = isActive + aggregatedSummary.userId = event.senderId + + deactivateAllPreviousBeacons(realm, roomId, event.senderId, targetEventId) + + if (isActive) { + scheduleDeactivationAfterTimeout(targetEventId, roomId, endOfLiveTimestampMillis) + } else { + cancelDeactivationAfterTimeout(targetEventId, roomId) + } + + return true } + private fun scheduleDeactivationAfterTimeout(eventId: String, roomId: String, endOfLiveTimestampMillis: Long?) { + endOfLiveTimestampMillis ?: return + + val workParams = DeactivateLiveLocationShareWorker.Params(sessionId = sessionId, eventId = eventId, roomId = roomId) + val workData = WorkerParamsFactory.toData(workParams) + val workName = DeactivateLiveLocationShareWorker.getWorkName(eventId = eventId, roomId = roomId) + val workDelayMillis = (endOfLiveTimestampMillis - clock.epochMillis()).coerceAtLeast(0) + Timber.d("scheduling deactivation of $eventId after $workDelayMillis millis") + val workRequest = workManagerProvider.matrixOneTimeWorkRequestBuilder() + .setInitialDelay(workDelayMillis, TimeUnit.MILLISECONDS) + .setInputData(workData) + .build() + + workManagerProvider.workManager.enqueueUniqueWork( + workName, + ExistingWorkPolicy.REPLACE, + workRequest + ) + } + + private fun cancelDeactivationAfterTimeout(eventId: String, roomId: String) { + val workName = DeactivateLiveLocationShareWorker.getWorkName(eventId = eventId, roomId = roomId) + workManagerProvider.workManager.cancelUniqueWork(workName) + } + + /** + * Handle the content of a beacon location data. + * @return true if it has been processed, false if ignored. + */ fun handleBeaconLocationData( realm: Realm, event: Event, @@ -67,14 +129,14 @@ internal class LiveLocationAggregationProcessor @Inject constructor() { roomId: String, relatedEventId: String?, isLocalEcho: Boolean - ) { + ): Boolean { if (event.senderId.isNullOrEmpty() || isLocalEcho) { - return + return false } if (relatedEventId.isNullOrEmpty()) { Timber.w("no related event id found for the live location content") - return + return false } val aggregatedSummary = LiveLocationShareAggregatedSummaryEntity.getOrCreate( @@ -89,11 +151,25 @@ internal class LiveLocationAggregationProcessor @Inject constructor() { ?.getBestTimestampMillis() ?: 0 - if (updatedLocationTimestamp.isMoreRecentThan(currentLocationTimestamp)) { + return if (updatedLocationTimestamp.isMoreRecentThan(currentLocationTimestamp)) { Timber.d("updating last location of the summary of id=$relatedEventId") aggregatedSummary.lastLocationContent = ContentMapper.map(content.toContent()) + true + } else { + false } } + private fun deactivateAllPreviousBeacons(realm: Realm, roomId: String, userId: String, currentEventId: String) { + LiveLocationShareAggregatedSummaryEntity + .findActiveLiveInRoomForUser( + realm = realm, + roomId = roomId, + userId = userId, + ignoredEventId = currentEventId + ) + .forEach { it.isActive = false } + } + 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/poll/DefaultPollAggregationProcessor.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/aggregation/poll/DefaultPollAggregationProcessor.kt index d4b414aaea..90d8e02c39 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/aggregation/poll/DefaultPollAggregationProcessor.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/aggregation/poll/DefaultPollAggregationProcessor.kt @@ -193,8 +193,10 @@ class DefaultPollAggregationProcessor @Inject constructor() : PollAggregationPro ?: EventAnnotationsSummaryEntity.create(realm, roomId, eventId) } - private fun getAggregatedPollSummaryEntity(realm: Realm, - eventAnnotationsSummaryEntity: EventAnnotationsSummaryEntity): PollResponseAggregatedSummaryEntity { + private fun getAggregatedPollSummaryEntity( + realm: Realm, + eventAnnotationsSummaryEntity: EventAnnotationsSummaryEntity + ): PollResponseAggregatedSummaryEntity { return eventAnnotationsSummaryEntity.pollResponseSummary ?: realm.createObject(PollResponseAggregatedSummaryEntity::class.java).also { eventAnnotationsSummaryEntity.pollResponseSummary = it 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 index 2546c58cc7..4f0228e6a8 100644 --- 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 @@ -61,10 +61,10 @@ internal class DefaultRoomCryptoService @AssistedInject constructor( (!force && isEncrypted() && encryptionAlgorithm() == MXCRYPTO_ALGORITHM_MEGOLM) -> { throw IllegalStateException("Encryption is already enabled for this room") } - (!force && algorithm != MXCRYPTO_ALGORITHM_MEGOLM) -> { + (!force && algorithm != MXCRYPTO_ALGORITHM_MEGOLM) -> { throw InvalidParameterException("Only MXCRYPTO_ALGORITHM_MEGOLM algorithm is supported") } - else -> { + else -> { val params = SendStateTask.Params( roomId = roomId, stateKey = "", diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/draft/DefaultDraftService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/draft/DefaultDraftService.kt index 3867e0dc8d..1ea8addb20 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/draft/DefaultDraftService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/draft/DefaultDraftService.kt @@ -26,9 +26,10 @@ import org.matrix.android.sdk.api.session.room.send.DraftService import org.matrix.android.sdk.api.session.room.send.UserDraft import org.matrix.android.sdk.api.util.Optional -internal class DefaultDraftService @AssistedInject constructor(@Assisted private val roomId: String, - private val draftRepository: DraftRepository, - private val coroutineDispatchers: MatrixCoroutineDispatchers +internal class DefaultDraftService @AssistedInject constructor( + @Assisted private val roomId: String, + private val draftRepository: DraftRepository, + private val coroutineDispatchers: MatrixCoroutineDispatchers ) : DraftService { @AssistedFactory diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/draft/DraftRepository.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/draft/DraftRepository.kt index c8c52c4b23..b672ef7e46 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/draft/DraftRepository.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/draft/DraftRepository.kt @@ -35,8 +35,10 @@ import org.matrix.android.sdk.internal.util.awaitTransaction import timber.log.Timber import javax.inject.Inject -internal class DraftRepository @Inject constructor(@SessionDatabase private val monarchy: Monarchy, - private val realmSessionProvider: RealmSessionProvider) { +internal class DraftRepository @Inject constructor( + @SessionDatabase private val monarchy: Monarchy, + private val realmSessionProvider: RealmSessionProvider +) { suspend fun saveDraft(roomId: String, userDraft: UserDraft) { monarchy.awaitTransaction { diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/location/CheckIfExistingActiveLiveTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/location/CheckIfExistingActiveLiveTask.kt new file mode 100644 index 0000000000..228a046f53 --- /dev/null +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/location/CheckIfExistingActiveLiveTask.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.session.room.location + +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.room.model.message.MessageBeaconInfoContent +import org.matrix.android.sdk.internal.task.Task +import javax.inject.Inject + +internal interface CheckIfExistingActiveLiveTask : Task { + data class Params( + val roomId: String, + ) +} + +internal class DefaultCheckIfExistingActiveLiveTask @Inject constructor( + private val getActiveBeaconInfoForUserTask: GetActiveBeaconInfoForUserTask, +) : CheckIfExistingActiveLiveTask { + + override suspend fun execute(params: CheckIfExistingActiveLiveTask.Params): Boolean { + val getActiveBeaconTaskParams = GetActiveBeaconInfoForUserTask.Params( + roomId = params.roomId + ) + return getActiveBeaconInfoForUserTask.execute(getActiveBeaconTaskParams) + ?.getClearContent() + ?.toModel() + ?.isLive + .orFalse() + } +} diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/location/DefaultLocationSharingService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/location/DefaultLocationSharingService.kt new file mode 100644 index 0000000000..20320cad23 --- /dev/null +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/location/DefaultLocationSharingService.kt @@ -0,0 +1,121 @@ +/* + * 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.location + +import androidx.lifecycle.LiveData +import androidx.lifecycle.Transformations +import com.zhuinden.monarchy.Monarchy +import dagger.assisted.Assisted +import dagger.assisted.AssistedFactory +import dagger.assisted.AssistedInject +import org.matrix.android.sdk.api.session.room.location.LocationSharingService +import org.matrix.android.sdk.api.session.room.location.UpdateLiveLocationShareResult +import org.matrix.android.sdk.api.session.room.model.livelocation.LiveLocationShareAggregatedSummary +import org.matrix.android.sdk.api.util.Cancelable +import org.matrix.android.sdk.api.util.Optional +import org.matrix.android.sdk.api.util.toOptional +import org.matrix.android.sdk.internal.database.mapper.LiveLocationShareAggregatedSummaryMapper +import org.matrix.android.sdk.internal.database.model.livelocation.LiveLocationShareAggregatedSummaryEntity +import org.matrix.android.sdk.internal.database.query.findRunningLiveInRoom +import org.matrix.android.sdk.internal.database.query.where +import org.matrix.android.sdk.internal.di.SessionDatabase + +internal class DefaultLocationSharingService @AssistedInject constructor( + @Assisted private val roomId: String, + @SessionDatabase private val monarchy: Monarchy, + private val sendStaticLocationTask: SendStaticLocationTask, + private val sendLiveLocationTask: SendLiveLocationTask, + private val startLiveLocationShareTask: StartLiveLocationShareTask, + private val stopLiveLocationShareTask: StopLiveLocationShareTask, + private val checkIfExistingActiveLiveTask: CheckIfExistingActiveLiveTask, + private val liveLocationShareAggregatedSummaryMapper: LiveLocationShareAggregatedSummaryMapper, +) : LocationSharingService { + + @AssistedFactory + interface Factory { + fun create(roomId: String): DefaultLocationSharingService + } + + override suspend fun sendStaticLocation(latitude: Double, longitude: Double, uncertainty: Double?, isUserLocation: Boolean): Cancelable { + val params = SendStaticLocationTask.Params( + roomId = roomId, + latitude = latitude, + longitude = longitude, + uncertainty = uncertainty, + isUserLocation = isUserLocation, + ) + return sendStaticLocationTask.execute(params) + } + + override suspend fun sendLiveLocation(beaconInfoEventId: String, latitude: Double, longitude: Double, uncertainty: Double?): Cancelable { + val params = SendLiveLocationTask.Params( + beaconInfoEventId = beaconInfoEventId, + roomId = roomId, + latitude = latitude, + longitude = longitude, + uncertainty = uncertainty, + ) + return sendLiveLocationTask.execute(params) + } + + override suspend fun startLiveLocationShare(timeoutMillis: Long): UpdateLiveLocationShareResult { + // Ensure to stop any active live before starting a new one + if (checkIfExistingActiveLive()) { + val result = stopLiveLocationShare() + if (result is UpdateLiveLocationShareResult.Failure) { + return result + } + } + val params = StartLiveLocationShareTask.Params( + roomId = roomId, + timeoutMillis = timeoutMillis + ) + return startLiveLocationShareTask.execute(params) + } + + private suspend fun checkIfExistingActiveLive(): Boolean { + val params = CheckIfExistingActiveLiveTask.Params( + roomId = roomId + ) + return checkIfExistingActiveLiveTask.execute(params) + } + + override suspend fun stopLiveLocationShare(): UpdateLiveLocationShareResult { + val params = StopLiveLocationShareTask.Params( + roomId = roomId, + ) + return stopLiveLocationShareTask.execute(params) + } + + override fun getRunningLiveLocationShareSummaries(): LiveData> { + return monarchy.findAllMappedWithChanges( + { LiveLocationShareAggregatedSummaryEntity.findRunningLiveInRoom(it, roomId = roomId) }, + liveLocationShareAggregatedSummaryMapper + ) + } + + override fun getLiveLocationShareSummary(beaconInfoEventId: String): LiveData> { + return Transformations.map( + monarchy.findAllMappedWithChanges( + { LiveLocationShareAggregatedSummaryEntity.where(it, roomId = roomId, eventId = beaconInfoEventId) }, + liveLocationShareAggregatedSummaryMapper + ) + ) { + it.firstOrNull().toOptional() + } + } +} diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/location/GetActiveBeaconInfoForUserTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/location/GetActiveBeaconInfoForUserTask.kt new file mode 100644 index 0000000000..a8d955af1d --- /dev/null +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/location/GetActiveBeaconInfoForUserTask.kt @@ -0,0 +1,54 @@ +/* + * 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.location + +import org.matrix.android.sdk.api.extensions.orFalse +import org.matrix.android.sdk.api.query.QueryStringValue +import org.matrix.android.sdk.api.session.events.model.Event +import org.matrix.android.sdk.api.session.events.model.EventType +import org.matrix.android.sdk.api.session.events.model.toModel +import org.matrix.android.sdk.api.session.room.model.message.MessageBeaconInfoContent +import org.matrix.android.sdk.internal.di.UserId +import org.matrix.android.sdk.internal.session.room.state.StateEventDataSource +import org.matrix.android.sdk.internal.task.Task +import javax.inject.Inject + +internal interface GetActiveBeaconInfoForUserTask : Task { + data class Params( + val roomId: String, + ) +} + +internal class DefaultGetActiveBeaconInfoForUserTask @Inject constructor( + @UserId private val userId: String, + private val stateEventDataSource: StateEventDataSource, +) : GetActiveBeaconInfoForUserTask { + + override suspend fun execute(params: GetActiveBeaconInfoForUserTask.Params): Event? { + return EventType.STATE_ROOM_BEACON_INFO + .mapNotNull { + stateEventDataSource.getStateEvent( + roomId = params.roomId, + eventType = it, + stateKey = QueryStringValue.Equals(userId) + ) + } + .firstOrNull { beaconInfoEvent -> + beaconInfoEvent.getClearContent()?.toModel()?.isLive.orFalse() + } + } +} diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/location/SendLiveLocationTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/location/SendLiveLocationTask.kt new file mode 100644 index 0000000000..bebd9c774a --- /dev/null +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/location/SendLiveLocationTask.kt @@ -0,0 +1,51 @@ +/* + * 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.location + +import org.matrix.android.sdk.api.util.Cancelable +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.task.Task +import javax.inject.Inject + +internal interface SendLiveLocationTask : Task { + data class Params( + val roomId: String, + val beaconInfoEventId: String, + val latitude: Double, + val longitude: Double, + val uncertainty: Double?, + ) +} + +internal class DefaultSendLiveLocationTask @Inject constructor( + private val localEchoEventFactory: LocalEchoEventFactory, + private val eventSenderProcessor: EventSenderProcessor, +) : SendLiveLocationTask { + + override suspend fun execute(params: SendLiveLocationTask.Params): Cancelable { + val event = localEchoEventFactory.createLiveLocationEvent( + beaconInfoEventId = params.beaconInfoEventId, + roomId = params.roomId, + latitude = params.latitude, + longitude = params.longitude, + uncertainty = params.uncertainty, + ) + localEchoEventFactory.createLocalEcho(event) + return eventSenderProcessor.postEvent(event) + } +} diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/location/SendStaticLocationTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/location/SendStaticLocationTask.kt new file mode 100644 index 0000000000..e08b82f3d4 --- /dev/null +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/location/SendStaticLocationTask.kt @@ -0,0 +1,51 @@ +/* + * 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.location + +import org.matrix.android.sdk.api.util.Cancelable +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.task.Task +import javax.inject.Inject + +internal interface SendStaticLocationTask : Task { + data class Params( + val roomId: String, + val latitude: Double, + val longitude: Double, + val uncertainty: Double?, + val isUserLocation: Boolean + ) +} + +internal class DefaultSendStaticLocationTask @Inject constructor( + private val localEchoEventFactory: LocalEchoEventFactory, + private val eventSenderProcessor: EventSenderProcessor, +) : SendStaticLocationTask { + + override suspend fun execute(params: SendStaticLocationTask.Params): Cancelable { + val event = localEchoEventFactory.createStaticLocationEvent( + roomId = params.roomId, + latitude = params.latitude, + longitude = params.longitude, + uncertainty = params.uncertainty, + isUserLocation = params.isUserLocation + ) + localEchoEventFactory.createLocalEcho(event) + return eventSenderProcessor.postEvent(event) + } +} diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/location/StartLiveLocationShareTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/location/StartLiveLocationShareTask.kt new file mode 100644 index 0000000000..b943c27977 --- /dev/null +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/location/StartLiveLocationShareTask.kt @@ -0,0 +1,66 @@ +/* + * Copyright (c) 2022 The Matrix.org Foundation C.I.C. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.matrix.android.sdk.internal.session.room.location + +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.room.location.UpdateLiveLocationShareResult +import org.matrix.android.sdk.api.session.room.model.message.MessageBeaconInfoContent +import org.matrix.android.sdk.internal.di.UserId +import org.matrix.android.sdk.internal.session.room.state.SendStateTask +import org.matrix.android.sdk.internal.task.Task +import org.matrix.android.sdk.internal.util.time.Clock +import javax.inject.Inject + +internal interface StartLiveLocationShareTask : Task { + data class Params( + val roomId: String, + val timeoutMillis: Long, + ) +} + +internal class DefaultStartLiveLocationShareTask @Inject constructor( + @UserId private val userId: String, + private val clock: Clock, + private val sendStateTask: SendStateTask, +) : StartLiveLocationShareTask { + + override suspend fun execute(params: StartLiveLocationShareTask.Params): UpdateLiveLocationShareResult { + val beaconContent = MessageBeaconInfoContent( + timeout = params.timeoutMillis, + isLive = true, + unstableTimestampMillis = clock.epochMillis() + ).toContent() + val eventType = EventType.STATE_ROOM_BEACON_INFO.first() + val sendStateTaskParams = SendStateTask.Params( + roomId = params.roomId, + stateKey = userId, + eventType = eventType, + body = beaconContent + ) + return try { + val eventId = sendStateTask.executeRetry(sendStateTaskParams, 3) + if (eventId.isNotEmpty()) { + UpdateLiveLocationShareResult.Success(eventId) + } else { + UpdateLiveLocationShareResult.Failure(Exception("empty event id for new state event")) + } + } catch (error: Throwable) { + UpdateLiveLocationShareResult.Failure(error) + } + } +} diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/location/StopLiveLocationShareTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/location/StopLiveLocationShareTask.kt new file mode 100644 index 0000000000..da5fd76940 --- /dev/null +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/location/StopLiveLocationShareTask.kt @@ -0,0 +1,72 @@ +/* + * 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.location + +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.location.UpdateLiveLocationShareResult +import org.matrix.android.sdk.api.session.room.model.message.MessageBeaconInfoContent +import org.matrix.android.sdk.internal.session.room.state.SendStateTask +import org.matrix.android.sdk.internal.task.Task +import javax.inject.Inject + +internal interface StopLiveLocationShareTask : Task { + data class Params( + val roomId: String, + ) +} + +internal class DefaultStopLiveLocationShareTask @Inject constructor( + private val sendStateTask: SendStateTask, + private val getActiveBeaconInfoForUserTask: GetActiveBeaconInfoForUserTask, +) : StopLiveLocationShareTask { + + override suspend fun execute(params: StopLiveLocationShareTask.Params): UpdateLiveLocationShareResult { + val beaconInfoStateEvent = getActiveLiveLocationBeaconInfoForUser(params.roomId) ?: return getResultForIncorrectBeaconInfoEvent() + val stateKey = beaconInfoStateEvent.stateKey ?: return getResultForIncorrectBeaconInfoEvent() + val content = beaconInfoStateEvent.getClearContent()?.toModel() ?: return getResultForIncorrectBeaconInfoEvent() + val updatedContent = content.copy(isLive = false).toContent() + val sendStateTaskParams = SendStateTask.Params( + roomId = params.roomId, + stateKey = stateKey, + eventType = EventType.STATE_ROOM_BEACON_INFO.first(), + body = updatedContent + ) + return try { + val eventId = sendStateTask.executeRetry(sendStateTaskParams, 3) + if (eventId.isNotEmpty()) { + UpdateLiveLocationShareResult.Success(eventId) + } else { + UpdateLiveLocationShareResult.Failure(Exception("empty event id for new state event")) + } + } catch (error: Throwable) { + UpdateLiveLocationShareResult.Failure(error) + } + } + + private fun getResultForIncorrectBeaconInfoEvent() = + UpdateLiveLocationShareResult.Failure(Exception("incorrect last beacon info event")) + + private suspend fun getActiveLiveLocationBeaconInfoForUser(roomId: String): Event? { + val params = GetActiveBeaconInfoForUserTask.Params( + roomId = roomId + ) + return getActiveBeaconInfoForUserTask.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 d3d1cb856a..ee8a2170bb 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 @@ -68,9 +68,9 @@ internal class DefaultLoadRoomMembersTask @Inject constructor( override suspend fun execute(params: LoadRoomMembersTask.Params) { when (getRoomMembersLoadStatus(params.roomId)) { - RoomMembersLoadStatusType.NONE -> doRequest(params) + RoomMembersLoadStatusType.NONE -> doRequest(params) RoomMembersLoadStatusType.LOADING -> waitPreviousRequestToFinish(params) - RoomMembersLoadStatusType.LOADED -> Unit + RoomMembersLoadStatusType.LOADED -> Unit } } @@ -114,7 +114,7 @@ internal class DefaultLoadRoomMembersTask @Inject constructor( if (roomMemberEvent.eventId == null || roomMemberEvent.stateKey == null || roomMemberEvent.type == null) { continue } - val ageLocalTs = roomMemberEvent.unsignedData?.age?.let { now - it } + val ageLocalTs = now - (roomMemberEvent.unsignedData?.age ?: 0) val eventEntity = roomMemberEvent.toEntity(roomId, SendState.SYNCED, ageLocalTs).copyToRealmOrIgnore(realm, EventInsertType.PAGINATION) CurrentStateEventEntity.getOrCreate( realm, diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/membership/RoomChangeMembershipStateDataSource.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/membership/RoomChangeMembershipStateDataSource.kt index 35d8cb08af..c61625b2b1 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/membership/RoomChangeMembershipStateDataSource.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/membership/RoomChangeMembershipStateDataSource.kt @@ -61,8 +61,8 @@ internal class RoomChangeMembershipStateDataSource @Inject constructor() { private fun Membership.toMembershipChangeState(): ChangeMembershipState { return when { this == Membership.JOIN -> ChangeMembershipState.Joined - this.isLeft() -> ChangeMembershipState.Left - else -> ChangeMembershipState.Unknown + this.isLeft() -> ChangeMembershipState.Left + else -> ChangeMembershipState.Unknown } } } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/membership/RoomDisplayNameResolver.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/membership/RoomDisplayNameResolver.kt index 9e672dcc5c..4645bb05ab 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/membership/RoomDisplayNameResolver.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/membership/RoomDisplayNameResolver.kt @@ -110,32 +110,32 @@ internal class RoomDisplayNameResolver @Inject constructor( } val otherMembersCount = otherMembersSubset.count() name = when (otherMembersCount) { - 0 -> { + 0 -> { // Get left members if any val leftMembersNames = roomMembers.queryLeftRoomMembersEvent() .findAll() .map { displayNameResolver.getBestName(it.toMatrixItem()) } roomDisplayNameFallbackProvider.getNameForEmptyRoom(roomSummary?.isDirect.orFalse(), leftMembersNames) } - 1 -> { + 1 -> { roomDisplayNameFallbackProvider.getNameFor1member( resolveRoomMemberName(otherMembersSubset[0], roomMembers) ) } - 2 -> { + 2 -> { roomDisplayNameFallbackProvider.getNameFor2members( resolveRoomMemberName(otherMembersSubset[0], roomMembers), resolveRoomMemberName(otherMembersSubset[1], roomMembers) ) } - 3 -> { + 3 -> { roomDisplayNameFallbackProvider.getNameFor3members( resolveRoomMemberName(otherMembersSubset[0], roomMembers), resolveRoomMemberName(otherMembersSubset[1], roomMembers), resolveRoomMemberName(otherMembersSubset[2], roomMembers) ) } - 4 -> { + 4 -> { roomDisplayNameFallbackProvider.getNameFor4members( resolveRoomMemberName(otherMembersSubset[0], roomMembers), resolveRoomMemberName(otherMembersSubset[1], roomMembers), @@ -158,8 +158,10 @@ internal class RoomDisplayNameResolver @Inject constructor( } /** See [org.matrix.android.sdk.api.session.room.sender.SenderInfo.disambiguatedDisplayName]. */ - private fun resolveRoomMemberName(roomMemberSummary: RoomMemberSummaryEntity, - roomMemberHelper: RoomMemberHelper): String { + private fun resolveRoomMemberName( + roomMemberSummary: RoomMemberSummaryEntity, + roomMemberHelper: RoomMemberHelper + ): String { val isUnique = roomMemberHelper.isUniqueDisplayName(roomMemberSummary.displayName) return if (isUnique) { displayNameResolver.getBestName(roomMemberSummary.toMatrixItem()) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/membership/RoomMemberEventHandler.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/membership/RoomMemberEventHandler.kt index a1b30a0be5..1e36e9c6da 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/membership/RoomMemberEventHandler.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/membership/RoomMemberEventHandler.kt @@ -34,11 +34,13 @@ internal class RoomMemberEventHandler @Inject constructor( @UserId private val myUserId: String ) { - fun handle(realm: Realm, - roomId: String, - event: Event, - isInitialSync: Boolean, - aggregator: SyncResponsePostTreatmentAggregator? = null): Boolean { + fun handle( + realm: Realm, + roomId: String, + event: Event, + isInitialSync: Boolean, + aggregator: SyncResponsePostTreatmentAggregator? = null + ): Boolean { if (event.type != EventType.STATE_ROOM_MEMBER) { return false } @@ -59,12 +61,14 @@ internal class RoomMemberEventHandler @Inject constructor( } } - private fun handleInitialSync(realm: Realm, - roomId: String, - currentUserId: String, - eventUserId: String, - roomMember: RoomMemberContent, - aggregator: SyncResponsePostTreatmentAggregator?): Boolean { + private fun handleInitialSync( + realm: Realm, + roomId: String, + currentUserId: String, + eventUserId: String, + roomMember: RoomMemberContent, + aggregator: SyncResponsePostTreatmentAggregator? + ): Boolean { if (currentUserId != eventUserId) { saveUserEntityLocallyIfNecessary(realm, eventUserId, roomMember) } @@ -73,10 +77,12 @@ internal class RoomMemberEventHandler @Inject constructor( return true } - private fun saveRoomMemberEntityLocally(realm: Realm, - roomId: String, - userId: String, - roomMember: RoomMemberContent) { + private fun saveRoomMemberEntityLocally( + realm: Realm, + roomId: String, + userId: String, + roomMember: RoomMemberContent + ) { val roomMemberEntity = RoomMemberEntityFactory.create( roomId, userId, @@ -96,9 +102,11 @@ internal class RoomMemberEventHandler @Inject constructor( return RoomMemberSummaryEntity.where(realm, roomId, userId).findFirst()?.userPresenceEntity } - private fun saveUserEntityLocallyIfNecessary(realm: Realm, - userId: String, - roomMember: RoomMemberContent) { + private fun saveUserEntityLocallyIfNecessary( + realm: Realm, + userId: String, + roomMember: RoomMemberContent + ) { if (roomMember.membership.isActive()) { saveUserLocally(realm, userId, roomMember) } @@ -109,9 +117,11 @@ internal class RoomMemberEventHandler @Inject constructor( realm.insertOrUpdate(userEntity) } - private fun updateDirectChatsIfNecessary(roomId: String, - roomMember: RoomMemberContent, - aggregator: SyncResponsePostTreatmentAggregator?) { + private fun updateDirectChatsIfNecessary( + roomId: String, + roomMember: RoomMemberContent, + aggregator: SyncResponsePostTreatmentAggregator? + ) { // check whether this new room member event may be used to update the directs dictionary in account data // this is required to handle correctly invite by email in DM val mxId = roomMember.thirdPartyInvite?.signed?.mxid @@ -120,12 +130,14 @@ internal class RoomMemberEventHandler @Inject constructor( } } - private fun handleIncrementalSync(realm: Realm, - roomId: String, - eventUserId: String, - roomMember: RoomMemberContent, - prevContent: Content?, - aggregator: SyncResponsePostTreatmentAggregator?): Boolean { + private fun handleIncrementalSync( + realm: Realm, + roomId: String, + eventUserId: String, + roomMember: RoomMemberContent, + prevContent: Content?, + aggregator: SyncResponsePostTreatmentAggregator? + ): Boolean { if (aggregator != null) { val previousDisplayName = prevContent?.get("displayname") as? String val previousAvatar = prevContent?.get("avatar_url") as? String diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/membership/RoomMemberHelper.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/membership/RoomMemberHelper.kt index 40228fe8c9..7da12a75de 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/membership/RoomMemberHelper.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/membership/RoomMemberHelper.kt @@ -33,8 +33,9 @@ import org.matrix.android.sdk.internal.database.query.where * It allows to get the live membership of a user. */ -internal class RoomMemberHelper(private val realm: Realm, - private val roomId: String +internal class RoomMemberHelper( + private val realm: Realm, + private val roomId: String ) { private val roomSummary: RoomSummaryEntity? by lazy { diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/membership/admin/MembershipAdminTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/membership/admin/MembershipAdminTask.kt index d2c21f3520..b07a77a18f 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/membership/admin/MembershipAdminTask.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/membership/admin/MembershipAdminTask.kt @@ -43,9 +43,9 @@ internal class DefaultMembershipAdminTask @Inject constructor(private val roomAP val userIdAndReason = UserIdAndReason(params.userId, params.reason) executeRequest(null) { when (params.type) { - MembershipAdminTask.Type.BAN -> roomAPI.ban(params.roomId, userIdAndReason) + MembershipAdminTask.Type.BAN -> roomAPI.ban(params.roomId, userIdAndReason) MembershipAdminTask.Type.UNBAN -> roomAPI.unban(params.roomId, userIdAndReason) - MembershipAdminTask.Type.KICK -> roomAPI.kick(params.roomId, userIdAndReason) + MembershipAdminTask.Type.KICK -> roomAPI.kick(params.roomId, userIdAndReason) } } } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/membership/leaving/LeaveRoomTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/membership/leaving/LeaveRoomTask.kt index 1b836e36a6..dd28bbcc73 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/membership/leaving/LeaveRoomTask.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/membership/leaving/LeaveRoomTask.kt @@ -60,7 +60,7 @@ internal class DefaultLeaveRoomTask @Inject constructor( val roomCreateStateEvent = stateEventDataSource.getStateEvent( roomId = roomId, eventType = EventType.STATE_ROOM_CREATE, - stateKey = QueryStringValue.NoCondition + stateKey = QueryStringValue.IsEmpty, ) // Server is not cleaning predecessor rooms, so we also try to left them val predecessorRoomId = roomCreateStateEvent?.getClearContent()?.toModel()?.predecessor?.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 85f53e1346..d46100a3a8 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 @@ -29,9 +29,11 @@ import org.matrix.android.sdk.internal.database.model.PushRuleEntity import org.matrix.android.sdk.internal.database.query.where 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) : +internal class DefaultRoomPushRuleService @AssistedInject constructor( + @Assisted private val roomId: String, + private val setRoomNotificationStateTask: SetRoomNotificationStateTask, + @SessionDatabase private val monarchy: Monarchy +) : RoomPushRuleService { @AssistedFactory 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 a5a5ab58ba..42b069f8fa 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 @@ -33,10 +33,10 @@ internal fun PushRuleEntity.toRoomPushRule(): RoomPushRule? { RuleSetKey.OVERRIDE -> { PushRulesMapper.map(this) } - RuleSetKey.ROOM -> { + RuleSetKey.ROOM -> { PushRulesMapper.mapRoomRule(this) } - else -> null + else -> null } return if (pushRule == null || kind == null) { null @@ -47,7 +47,7 @@ internal fun PushRuleEntity.toRoomPushRule(): RoomPushRule? { internal fun RoomNotificationState.toRoomPushRule(roomId: String): RoomPushRule? { return when { - this == RoomNotificationState.ALL_MESSAGES -> null + this == RoomNotificationState.ALL_MESSAGES -> null this == RoomNotificationState.ALL_MESSAGES_NOISY -> { val rule = PushRule( actions = listOf(Action.Notify, Action.Sound()).toJson(), @@ -56,7 +56,7 @@ internal fun RoomNotificationState.toRoomPushRule(roomId: String): RoomPushRule? ) return RoomPushRule(RuleSetKey.ROOM, rule) } - else -> { + else -> { val condition = PushCondition( kind = Kind.EventMatch.value, key = "room_id", 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 021d7dbefb..3d51d19cba 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 @@ -35,9 +35,11 @@ internal interface SetRoomNotificationStateTask : Task { + else -> { // RoomDirectoryVisibility.PRIVATE or null // We cannot resolve this room :/ null diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/prune/RedactionEventProcessor.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/prune/RedactionEventProcessor.kt index e926d6a785..e33fbb56b1 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/prune/RedactionEventProcessor.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/prune/RedactionEventProcessor.kt @@ -145,10 +145,10 @@ internal class RedactionEventProcessor @Inject constructor() : EventInsertLivePr private fun computeAllowedKeys(type: String): List { // Add filtered content, allowed keys in content depends on the event type return when (type) { - EventType.STATE_ROOM_MEMBER -> listOf("membership") - EventType.STATE_ROOM_CREATE -> listOf("creator") - EventType.STATE_ROOM_JOIN_RULES -> listOf("join_rule") - EventType.STATE_ROOM_POWER_LEVELS -> listOf( + EventType.STATE_ROOM_MEMBER -> listOf("membership") + EventType.STATE_ROOM_CREATE -> listOf("creator") + EventType.STATE_ROOM_JOIN_RULES -> listOf("join_rule") + EventType.STATE_ROOM_POWER_LEVELS -> listOf( "users", "users_default", "events", @@ -159,10 +159,10 @@ internal class RedactionEventProcessor @Inject constructor() : EventInsertLivePr "redact", "invite" ) - EventType.STATE_ROOM_ALIASES -> listOf("aliases") + EventType.STATE_ROOM_ALIASES -> listOf("aliases") EventType.STATE_ROOM_CANONICAL_ALIAS -> listOf("alias") - EventType.FEEDBACK -> listOf("type", "target_event_id") - else -> emptyList() + EventType.FEEDBACK -> listOf("type", "target_event_id") + else -> emptyList() } } } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/relation/DefaultRelationService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/relation/DefaultRelationService.kt index 7b68e2a74c..9839a44427 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/relation/DefaultRelationService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/relation/DefaultRelationService.kt @@ -92,25 +92,31 @@ internal class DefaultRelationService @AssistedInject constructor( } } - override fun editPoll(targetEvent: TimelineEvent, - pollType: PollType, - question: String, - options: List): Cancelable { + override fun editPoll( + targetEvent: TimelineEvent, + pollType: PollType, + question: String, + options: List + ): Cancelable { return eventEditor.editPoll(targetEvent, pollType, question, options) } - override fun editTextMessage(targetEvent: TimelineEvent, - msgType: String, - newBodyText: CharSequence, - newBodyAutoMarkdown: Boolean, - compatibilityBodyText: String): Cancelable { + override fun editTextMessage( + targetEvent: TimelineEvent, + msgType: String, + newBodyText: CharSequence, + newBodyAutoMarkdown: Boolean, + compatibilityBodyText: String + ): Cancelable { return eventEditor.editTextMessage(targetEvent, msgType, newBodyText, newBodyAutoMarkdown, compatibilityBodyText) } - override fun editReply(replyToEdit: TimelineEvent, - originalTimelineEvent: TimelineEvent, - newBodyText: String, - compatibilityBodyText: String): Cancelable { + override fun editReply( + replyToEdit: TimelineEvent, + originalTimelineEvent: TimelineEvent, + newBodyText: String, + compatibilityBodyText: String + ): Cancelable { return eventEditor.editReply(replyToEdit, originalTimelineEvent, newBodyText, compatibilityBodyText) } @@ -164,7 +170,8 @@ internal class DefaultRelationService @AssistedInject constructor( msgType: String, autoMarkdown: Boolean, formattedText: String?, - eventReplied: TimelineEvent?): Cancelable? { + eventReplied: TimelineEvent? + ): Cancelable? { val event = if (eventReplied != null) { // Reply within a thread eventFactory.createReplyTextEvent( diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/relation/EventEditor.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/relation/EventEditor.kt index 7bf7d6b587..795e9003ce 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/relation/EventEditor.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/relation/EventEditor.kt @@ -38,11 +38,13 @@ internal class EventEditor @Inject constructor( private val clock: Clock, ) { - fun editTextMessage(targetEvent: TimelineEvent, - msgType: String, - newBodyText: CharSequence, - newBodyAutoMarkdown: Boolean, - compatibilityBodyText: String): Cancelable { + fun editTextMessage( + targetEvent: TimelineEvent, + msgType: String, + newBodyText: CharSequence, + newBodyAutoMarkdown: Boolean, + compatibilityBodyText: String + ): Cancelable { val roomId = targetEvent.roomId if (targetEvent.root.sendState.hasFailed()) { // We create a new in memory event for the EventSenderProcessor but we keep the eventId of the failed event. @@ -61,10 +63,12 @@ internal class EventEditor @Inject constructor( } } - fun editPoll(targetEvent: TimelineEvent, - pollType: PollType, - question: String, - options: List): Cancelable { + fun editPoll( + targetEvent: TimelineEvent, + pollType: PollType, + question: String, + options: List + ): Cancelable { val roomId = targetEvent.roomId if (targetEvent.root.sendState.hasFailed()) { val editedEvent = eventFactory.createPollEvent(roomId, pollType, question, options).copy( @@ -92,10 +96,12 @@ internal class EventEditor @Inject constructor( return eventSenderProcessor.postEvent(editedEvent) } - fun editReply(replyToEdit: TimelineEvent, - originalTimelineEvent: TimelineEvent, - newBodyText: String, - compatibilityBodyText: String): Cancelable { + fun editReply( + replyToEdit: TimelineEvent, + originalTimelineEvent: TimelineEvent, + newBodyText: String, + compatibilityBodyText: String + ): Cancelable { val roomId = replyToEdit.roomId if (replyToEdit.root.sendState.hasFailed()) { // We create a new in memory event for the EventSenderProcessor but we keep the eventId of the failed event. diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/relation/FindReactionEventForUndoTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/relation/FindReactionEventForUndoTask.kt index f7cfa3489d..585f896f32 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/relation/FindReactionEventForUndoTask.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/relation/FindReactionEventForUndoTask.kt @@ -41,7 +41,8 @@ internal interface FindReactionEventForUndoTask : Task diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/relation/UpdateQuickReactionTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/relation/UpdateQuickReactionTask.kt index d0ab430dad..24e8ba7f9e 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/relation/UpdateQuickReactionTask.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/relation/UpdateQuickReactionTask.kt @@ -41,8 +41,10 @@ internal interface UpdateQuickReactionTask : Task?>? = null 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 c5f9bd13fd..254dee4295 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 @@ -77,8 +77,10 @@ internal class DefaultFetchThreadSummariesTask @Inject constructor( return handleResponse(response, params) } - private suspend fun handleResponse(response: PaginationResponse, - params: FetchThreadSummariesTask.Params): Result { + private suspend fun handleResponse( + response: PaginationResponse, + params: FetchThreadSummariesTask.Params + ): Result { val rootThreadList = response.events monarchy.awaitTransaction { realm -> val roomEntity = RoomEntity.where(realm, roomId = params.roomId).findFirst() ?: return@awaitTransaction 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 dabdd04f60..bac810f424 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 @@ -109,8 +109,10 @@ internal class DefaultFetchThreadTimelineTask @Inject constructor( return handleRelationsResponse(response, params) } - private suspend fun handleRelationsResponse(response: RelationsResponse, - params: FetchThreadTimelineTask.Params): Result { + private suspend fun handleRelationsResponse( + response: RelationsResponse, + params: FetchThreadTimelineTask.Params + ): Result { val threadList = response.chunks val threadRootEvent = response.originalEvent val hasReachEnd = response.nextBatch == null @@ -207,7 +209,8 @@ internal class DefaultFetchThreadTimelineTask @Inject constructor( * Create an EventEntity to be added in the TimelineEventEntity. */ private fun createEventEntity(roomId: String, event: Event, realm: Realm): EventEntity { - val ageLocalTs = event.unsignedData?.age?.let { clock.epochMillis() - it } + val now = clock.epochMillis() + val ageLocalTs = now - (event.unsignedData?.age ?: 0) return event.toEntity(roomId, SendState.SYNCED, ageLocalTs).copyToRealmOrIgnore(realm, EventInsertType.PAGINATION) } @@ -232,9 +235,11 @@ internal class DefaultFetchThreadTimelineTask @Inject constructor( } } - private fun handleReaction(realm: Realm, - event: Event, - roomId: String) { + private fun handleReaction( + realm: Realm, + event: Event, + roomId: String + ) { val unsignedData = event.unsignedData ?: return val relatedEventId = event.eventId ?: return diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/relationship/RoomChildRelationInfo.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/relationship/RoomChildRelationInfo.kt index 5bad334afc..fffca96acf 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/relationship/RoomChildRelationInfo.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/relationship/RoomChildRelationInfo.kt @@ -43,7 +43,6 @@ internal class RoomChildRelationInfo( data class SpaceChildInfo( val roomId: String, val order: String?, -// val autoJoin: Boolean, val viaServers: List ) @@ -60,18 +59,13 @@ internal class RoomChildRelationInfo( fun getDirectChildrenDescriptions(): List { return CurrentStateEventEntity.whereType(realm, roomId, EventType.STATE_SPACE_CHILD) .findAll() -// .also { -// Timber.v("## Space: Found ${it.count()} m.space.child state events for $roomId") -// } .mapNotNull { ContentMapper.map(it.root?.content).toModel()?.let { scc -> -// Timber.v("## Space child desc state event $scc") // Children where via is not present are ignored. scc.via?.let { via -> SpaceChildInfo( roomId = it.stateKey, order = scc.validOrder(), -// autoJoin = scc.autoJoin ?: false, viaServers = via ) } @@ -83,17 +77,13 @@ internal class RoomChildRelationInfo( fun getParentDescriptions(): List { return CurrentStateEventEntity.whereType(realm, roomId, EventType.STATE_SPACE_PARENT) .findAll() -// .also { -// Timber.v("## Space: Found ${it.count()} m.space.parent state events for $roomId") -// } .mapNotNull { - ContentMapper.map(it.root?.content).toModel()?.let { scc -> -// Timber.v("## Space parent desc state event $scc") + ContentMapper.map(it.root?.content).toModel()?.let { spaceParentContent -> // Parent where via is not present are ignored. - scc.via?.let { via -> + spaceParentContent.via?.let { via -> SpaceParentInfo( roomId = it.stateKey, - canonical = scc.canonical ?: false, + canonical = spaceParentContent.canonical ?: false, viaServers = via, stateEventSender = it.root?.sender ?: "" ) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/reporting/DefaultReportingService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/reporting/DefaultReportingService.kt index c961f718ef..1e32bf5e16 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/reporting/DefaultReportingService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/reporting/DefaultReportingService.kt @@ -21,8 +21,9 @@ import dagger.assisted.AssistedFactory import dagger.assisted.AssistedInject import org.matrix.android.sdk.api.session.room.reporting.ReportingService -internal class DefaultReportingService @AssistedInject constructor(@Assisted private val roomId: String, - private val reportContentTask: ReportContentTask +internal class DefaultReportingService @AssistedInject constructor( + @Assisted private val roomId: String, + private val reportContentTask: ReportContentTask ) : ReportingService { @AssistedFactory diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/DefaultSendService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/DefaultSendService.kt index 8529365858..418000abed 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/DefaultSendService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/DefaultSendService.kt @@ -129,18 +129,6 @@ internal class DefaultSendService @AssistedInject constructor( .let { sendEvent(it) } } - override fun sendLocation(latitude: Double, longitude: Double, uncertainty: Double?, isUserLocation: Boolean): Cancelable { - return localEchoEventFactory.createLocationEvent(roomId, latitude, longitude, uncertainty, isUserLocation) - .also { createLocalEcho(it) } - .let { sendEvent(it) } - } - - override fun sendLiveLocation(beaconInfoEventId: String, latitude: Double, longitude: Double, uncertainty: Double?): Cancelable { - return localEchoEventFactory.createLiveLocationEvent(beaconInfoEventId, roomId, latitude, longitude, uncertainty) - .also { createLocalEcho(it) } - .let { sendEvent(it) } - } - override fun redactEvent(event: Event, reason: String?): Cancelable { // TODO manage media/attachements? val redactionEcho = localEchoEventFactory.createRedactEvent(roomId, event.eventId!!, reason) @@ -198,7 +186,7 @@ internal class DefaultSendService @AssistedInject constructor( localEchoRepository.updateSendState(localEcho.eventId, roomId, SendState.UNSENT) internalSendMedia(listOf(localEcho.root), attachmentData, true) } - is MessageFileContent -> { + is MessageFileContent -> { val attachmentData = ContentAttachmentData( size = messageContent.info!!.size, mimeType = messageContent.mimeType, @@ -222,7 +210,7 @@ internal class DefaultSendService @AssistedInject constructor( localEchoRepository.updateSendState(localEcho.eventId, roomId, SendState.UNSENT) internalSendMedia(listOf(localEcho.root), attachmentData, true) } - else -> NoOpCancellable + else -> NoOpCancellable } } return NoOpCancellable @@ -265,10 +253,11 @@ internal class DefaultSendService @AssistedInject constructor( } } - override fun sendMedias(attachments: List, - compressBeforeSending: Boolean, - roomIds: Set, - rootThreadEventId: String? + override fun sendMedias( + attachments: List, + compressBeforeSending: Boolean, + roomIds: Set, + rootThreadEventId: String? ): Cancelable { return attachments.mapTo(CancelableBag()) { sendMedia( @@ -280,10 +269,11 @@ internal class DefaultSendService @AssistedInject constructor( } } - override fun sendMedia(attachment: ContentAttachmentData, - compressBeforeSending: Boolean, - roomIds: Set, - rootThreadEventId: String? + override fun sendMedia( + attachment: ContentAttachmentData, + compressBeforeSending: Boolean, + roomIds: Set, + rootThreadEventId: String? ): Cancelable { // Ensure that the event will not be send in a thread if we are a different flow. // Like sending files to multiple rooms @@ -354,10 +344,12 @@ internal class DefaultSendService @AssistedInject constructor( return "${roomId}_$identifier" } - private fun createUploadMediaWork(allLocalEchos: List, - attachment: ContentAttachmentData, - isRoomEncrypted: Boolean, - compressBeforeSending: Boolean): OneTimeWorkRequest { + private fun createUploadMediaWork( + allLocalEchos: List, + attachment: ContentAttachmentData, + isRoomEncrypted: Boolean, + compressBeforeSending: Boolean + ): OneTimeWorkRequest { val localEchoIds = allLocalEchos.map { LocalEchoIdentifiers(it.roomId!!, it.eventId!!) } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/LocalEchoEventFactory.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/LocalEchoEventFactory.kt index d39088bd6d..f52500de1b 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/LocalEchoEventFactory.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/LocalEchoEventFactory.kt @@ -119,12 +119,14 @@ internal class LocalEchoEventFactory @Inject constructor( return createMessageEvent(roomId, textContent.toMessageTextContent(msgType)) } - fun createReplaceTextEvent(roomId: String, - targetEventId: String, - newBodyText: CharSequence, - newBodyAutoMarkdown: Boolean, - msgType: String, - compatibilityText: String): Event { + fun createReplaceTextEvent( + roomId: String, + targetEventId: String, + newBodyText: CharSequence, + newBodyAutoMarkdown: Boolean, + msgType: String, + compatibilityText: String + ): Event { return createMessageEvent( roomId, MessageTextContent( @@ -138,9 +140,11 @@ internal class LocalEchoEventFactory @Inject constructor( ) } - private fun createPollContent(question: String, - options: List, - pollType: PollType): MessagePollContent { + private fun createPollContent( + question: String, + options: List, + pollType: PollType + ): MessagePollContent { return MessagePollContent( unstablePollCreationInfo = PollCreationInfo( question = PollQuestion(unstableQuestion = question), @@ -152,11 +156,13 @@ internal class LocalEchoEventFactory @Inject constructor( ) } - fun createPollReplaceEvent(roomId: String, - pollType: PollType, - targetEventId: String, - question: String, - options: List): Event { + fun createPollReplaceEvent( + roomId: String, + pollType: PollType, + targetEventId: String, + question: String, + options: List + ): Event { val newContent = MessagePollContent( relatesTo = RelationDefaultContent(RelationType.REPLACE, targetEventId), newContent = createPollContent(question, options, pollType).toContent() @@ -172,9 +178,11 @@ internal class LocalEchoEventFactory @Inject constructor( ) } - fun createPollReplyEvent(roomId: String, - pollEventId: String, - answerId: String): Event { + fun createPollReplyEvent( + roomId: String, + pollEventId: String, + answerId: String + ): Event { val content = MessagePollResponseContent( body = answerId, relatesTo = RelationDefaultContent( @@ -195,10 +203,12 @@ internal class LocalEchoEventFactory @Inject constructor( ) } - fun createPollEvent(roomId: String, - pollType: PollType, - question: String, - options: List): Event { + fun createPollEvent( + roomId: String, + pollType: PollType, + question: String, + options: List + ): Event { val content = createPollContent(question, options, pollType) val localId = LocalEcho.createLocalEchoId() return Event( @@ -212,8 +222,10 @@ internal class LocalEchoEventFactory @Inject constructor( ) } - fun createEndPollEvent(roomId: String, - eventId: String): Event { + fun createEndPollEvent( + roomId: String, + eventId: String + ): Event { val content = MessageEndPollContent( relatesTo = RelationDefaultContent( type = RelationType.REFERENCE, @@ -232,11 +244,13 @@ internal class LocalEchoEventFactory @Inject constructor( ) } - fun createLocationEvent(roomId: String, - latitude: Double, - longitude: Double, - uncertainty: Double?, - isUserLocation: Boolean): Event { + fun createStaticLocationEvent( + roomId: String, + latitude: Double, + longitude: Double, + uncertainty: Double?, + isUserLocation: Boolean + ): Event { val geoUri = buildGeoUri(latitude, longitude, uncertainty) val assetType = if (isUserLocation) LocationAssetType.SELF else LocationAssetType.PIN val content = MessageLocationContent( @@ -250,11 +264,13 @@ internal class LocalEchoEventFactory @Inject constructor( return createMessageEvent(roomId, content) } - fun createLiveLocationEvent(beaconInfoEventId: String, - roomId: String, - latitude: Double, - longitude: Double, - uncertainty: Double?): Event { + fun createLiveLocationEvent( + beaconInfoEventId: String, + roomId: String, + latitude: Double, + longitude: Double, + uncertainty: Double? + ): Event { val geoUri = buildGeoUri(latitude, longitude, uncertainty) val content = MessageBeaconLocationDataContent( body = geoUri, @@ -277,13 +293,15 @@ internal class LocalEchoEventFactory @Inject constructor( ) } - fun createReplaceTextOfReply(roomId: String, - eventReplaced: TimelineEvent, - originalEvent: TimelineEvent, - newBodyText: String, - autoMarkdown: Boolean, - msgType: String, - compatibilityText: String): Event { + fun createReplaceTextOfReply( + roomId: String, + eventReplaced: TimelineEvent, + originalEvent: TimelineEvent, + newBodyText: String, + autoMarkdown: Boolean, + msgType: String, + compatibilityText: String + ): Event { val permalink = permalinkFactory.createPermalink(roomId, originalEvent.root.eventId ?: "", false) val userLink = originalEvent.root.senderId?.let { permalinkFactory.createPermalink(it, false) } ?: "" @@ -321,16 +339,17 @@ internal class LocalEchoEventFactory @Inject constructor( ) } - fun createMediaEvent(roomId: String, - attachment: ContentAttachmentData, - rootThreadEventId: String? + fun createMediaEvent( + roomId: String, + attachment: ContentAttachmentData, + rootThreadEventId: String? ): Event { return when (attachment.type) { - ContentAttachmentData.Type.IMAGE -> createImageEvent(roomId, attachment, rootThreadEventId) - ContentAttachmentData.Type.VIDEO -> createVideoEvent(roomId, attachment, rootThreadEventId) - ContentAttachmentData.Type.AUDIO -> createAudioEvent(roomId, attachment, isVoiceMessage = false, rootThreadEventId = rootThreadEventId) + ContentAttachmentData.Type.IMAGE -> createImageEvent(roomId, attachment, rootThreadEventId) + ContentAttachmentData.Type.VIDEO -> createVideoEvent(roomId, attachment, rootThreadEventId) + ContentAttachmentData.Type.AUDIO -> createAudioEvent(roomId, attachment, isVoiceMessage = false, rootThreadEventId = rootThreadEventId) ContentAttachmentData.Type.VOICE_MESSAGE -> createAudioEvent(roomId, attachment, isVoiceMessage = true, rootThreadEventId = rootThreadEventId) - ContentAttachmentData.Type.FILE -> createFileEvent(roomId, attachment, rootThreadEventId) + ContentAttachmentData.Type.FILE -> createFileEvent(roomId, attachment, rootThreadEventId) } } @@ -435,10 +454,11 @@ internal class LocalEchoEventFactory @Inject constructor( return createMessageEvent(roomId, content) } - private fun createAudioEvent(roomId: String, - attachment: ContentAttachmentData, - isVoiceMessage: Boolean, - rootThreadEventId: String? + private fun createAudioEvent( + roomId: String, + attachment: ContentAttachmentData, + isVoiceMessage: Boolean, + rootThreadEventId: String? ): Event { val content = MessageAudioContent( msgType = MessageType.MSGTYPE_AUDIO, @@ -534,7 +554,8 @@ internal class LocalEchoEventFactory @Inject constructor( text: CharSequence, msgType: String, autoMarkdown: Boolean, - formattedText: String?): Event { + formattedText: String? + ): Event { val content = formattedText?.let { TextContent(text.toString(), it) } ?: createTextContent(text, autoMarkdown) return createEvent( roomId, @@ -555,12 +576,14 @@ internal class LocalEchoEventFactory @Inject constructor( /** * Creates a reply to a regular timeline Event or a thread Event if needed. */ - fun createReplyTextEvent(roomId: String, - eventReplied: TimelineEvent, - replyText: CharSequence, - autoMarkdown: Boolean, - rootThreadEventId: String? = null, - showInThread: Boolean): Event? { + fun createReplyTextEvent( + roomId: String, + eventReplied: TimelineEvent, + replyText: CharSequence, + autoMarkdown: Boolean, + rootThreadEventId: String? = null, + showInThread: Boolean + ): Event? { // Fallbacks and event representation // TODO Add error/warning logs when any of this is null val permalink = permalinkFactory.createPermalink(eventReplied.root, false) ?: return null @@ -662,7 +685,7 @@ internal class LocalEchoEventFactory @Inject constructor( when (content?.msgType) { MessageType.MSGTYPE_EMOTE, MessageType.MSGTYPE_TEXT, - MessageType.MSGTYPE_NOTICE -> { + MessageType.MSGTYPE_NOTICE -> { var formattedText: String? = null if (content is MessageContentWithFormattedBody) { formattedText = content.matrixFormattedBody @@ -673,19 +696,19 @@ internal class LocalEchoEventFactory @Inject constructor( TextContent(content.body, formattedText) } } - MessageType.MSGTYPE_FILE -> return TextContent("sent a file.") - MessageType.MSGTYPE_AUDIO -> return TextContent("sent an audio file.") - MessageType.MSGTYPE_IMAGE -> return TextContent("sent an image.") - MessageType.MSGTYPE_VIDEO -> return TextContent("sent a video.") + MessageType.MSGTYPE_FILE -> return TextContent("sent a file.") + MessageType.MSGTYPE_AUDIO -> return TextContent("sent an audio file.") + MessageType.MSGTYPE_IMAGE -> return TextContent("sent an image.") + MessageType.MSGTYPE_VIDEO -> return TextContent("sent a video.") MessageType.MSGTYPE_POLL_START -> { return TextContent((content as? MessagePollContent)?.getBestPollCreationInfo()?.question?.getBestQuestion() ?: "") } - else -> return TextContent(content?.body ?: "") + else -> return TextContent(content?.body ?: "") } } /** - * Returns RFC5870 formatted geo uri 'geo:latitude,longitude;uncertainty' like 'geo:40.05,29.24;30' + * Returns RFC5870 formatted geo uri 'geo:latitude,longitude;u=uncertainty' like 'geo:40.05,29.24;u=30' * Uncertainty of the location is in meters and not required. */ private fun buildGeoUri(latitude: Double, longitude: Double, uncertainty: Double?): String { @@ -695,7 +718,7 @@ internal class LocalEchoEventFactory @Inject constructor( append(",") append(longitude) uncertainty?.let { - append(";") + append(";u=") append(it) } } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/LocalEchoRepository.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/LocalEchoRepository.kt index bed590fd09..394cb8944f 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/LocalEchoRepository.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/LocalEchoRepository.kt @@ -201,7 +201,7 @@ internal class LocalEchoRepository @Inject constructor( // need to resend the attachment true } - else -> { + else -> { Timber.e("Cannot resend message ${event.root.getClearType()} / ${content.msgType}") false } @@ -211,7 +211,7 @@ internal class LocalEchoRepository @Inject constructor( false } } - else -> { + else -> { Timber.e("Unsupported message to resend ${event.root.getClearType()}") false } 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 1c0da4839a..765c282b65 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 @@ -71,7 +71,7 @@ internal class RedactEventWorker(context: Context, params: WorkerParameters, ses { when (it) { is Failure.NetworkConnection -> Result.retry() - else -> { + else -> { // TODO mark as failed to send? // always return success, or the chain will be stuck for ever! Result.success( diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/TextContentExtension.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/TextContentExtension.kt index 49bc05f40c..c5728ba527 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/TextContentExtension.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/TextContentExtension.kt @@ -43,7 +43,8 @@ internal fun TextContent.toMessageTextContent(msgType: String = MessageType.MSGT internal fun TextContent.toThreadTextContent( rootThreadEventId: String, latestThreadEventId: String, - msgType: String = MessageType.MSGTYPE_TEXT): MessageTextContent { + msgType: String = MessageType.MSGTYPE_TEXT +): MessageTextContent { return MessageTextContent( msgType = msgType, format = MessageFormat.FORMAT_MATRIX_HTML.takeIf { formattedText != null }, diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/WaveFormSanitizer.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/WaveFormSanitizer.kt index 78a03f3775..c7ca2e770f 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/WaveFormSanitizer.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/WaveFormSanitizer.kt @@ -58,7 +58,7 @@ internal class WaveFormSanitizer @Inject constructor() { } } } - else -> { + else -> { sizeInRangeList.addAll(waveForm) } } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/pills/MentionLinkSpecComparator.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/pills/MentionLinkSpecComparator.kt index 3d235ec4d6..db5eec7e7e 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/pills/MentionLinkSpecComparator.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/pills/MentionLinkSpecComparator.kt @@ -24,9 +24,9 @@ internal class MentionLinkSpecComparator @Inject constructor() : Comparator -1 o1.start > o2.start -> 1 - o1.end < o2.end -> 1 - o1.end > o2.end -> -1 - else -> 0 + o1.end < o2.end -> 1 + o1.end > o2.end -> -1 + else -> 0 } } } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/pills/TextPillsUtils.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/pills/TextPillsUtils.kt index fa2e0052ab..38bddae951 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/pills/TextPillsUtils.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/pills/TextPillsUtils.kt @@ -90,7 +90,7 @@ internal class TextPillsUtils @Inject constructor( // test if there is an overlap if (b.start in a.start until a.end) { when { - b.end <= a.end -> + b.end <= a.end -> // b is inside a -> b should be removed remove = i + 1 a.end - a.start > b.end - b.start -> 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 8ef631ad36..2c7eea1e54 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 @@ -150,13 +150,13 @@ internal class EventSenderProcessorCoroutine @Inject constructor( canReachServer.set(false) task.markAsFailedOrRetry(exception, 0) } - (exception.isLimitExceededError()) -> { + (exception.isLimitExceededError()) -> { task.markAsFailedOrRetry(exception, exception.getRetryDelay(3_000)) } - exception is CancellationException -> { + exception is CancellationException -> { Timber.v("## $task has been cancelled, try next task") } - else -> { + else -> { Timber.v("## un-retryable error for $task, try next task") // this task is in error, check next one? task.onTaskFailed() 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 301f8cb9d6..c5b13043d7 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 @@ -180,7 +180,7 @@ internal class EventSenderProcessorThread @Inject constructor( waitForNetwork() } } - (exception.isLimitExceededError()) -> { + (exception.isLimitExceededError()) -> { if (task.retryCount.getAndIncrement() >= 3) task.onTaskFailed() Timber.v("## SendThread retryLoop retryable error for $task reason: ${exception.localizedMessage}") // wait a bit @@ -188,17 +188,17 @@ internal class EventSenderProcessorThread @Inject constructor( sleep(3_000) continue@retryLoop } - exception.isTokenError() -> { + exception.isTokenError() -> { Timber.v("## SendThread retryLoop retryable TOKEN error, interrupt") // we can exit the loop task.onTaskFailed() throw InterruptedException() } - exception is CancellationException -> { + exception is CancellationException -> { Timber.v("## SendThread task has been cancelled") break@retryLoop } - else -> { + else -> { Timber.v("## SendThread retryLoop Un-Retryable error, try next task") // this task is in error, check next one? task.onTaskFailed() 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 545fc41737..0eedd4bd4d 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 @@ -35,11 +35,13 @@ import javax.inject.Inject private const val PERSISTENCE_KEY = "ManagedBySender" -internal class QueueMemento @Inject constructor(context: Context, - @SessionId sessionId: String, - private val queuedTaskFactory: QueuedTaskFactory, - private val localEchoRepository: LocalEchoRepository, - private val cryptoService: CryptoService) { +internal class QueueMemento @Inject constructor( + context: Context, + @SessionId sessionId: String, + private val queuedTaskFactory: QueuedTaskFactory, + private val localEchoRepository: LocalEchoRepository, + private val cryptoService: CryptoService +) { private val storage = context.getSharedPreferences("QueueMemento_$sessionId", Context.MODE_PRIVATE) private val trackedTasks = mutableListOf() @@ -74,11 +76,11 @@ internal class QueueMemento @Inject constructor(context: Context, encrypt = task.encrypt, order = order ) - is RedactQueuedTask -> RedactEventTaskInfo( + is RedactQueuedTask -> RedactEventTaskInfo( redactionLocalEcho = task.redactionLocalEchoId, order = order ) - else -> null + else -> null } } @@ -92,7 +94,7 @@ internal class QueueMemento @Inject constructor(context: Context, ?.forEach { info -> try { when (info) { - is SendEventTaskInfo -> { + is SendEventTaskInfo -> { localEchoRepository.getUpToDateEcho(info.localEchoId)?.let { if (it.sendState.isSending() && it.eventId != null && it.roomId != null) { localEchoRepository.updateSendState(it.eventId, it.roomId, SendState.UNSENT) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/queue/SendEventQueuedTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/queue/SendEventQueuedTask.kt index 49492e7990..1ba4e09caf 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/queue/SendEventQueuedTask.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/queue/SendEventQueuedTask.kt @@ -45,7 +45,7 @@ internal class SendEventQueuedTask( localEchoRepository.deleteFailedEchoAsync(eventId = event.eventId, roomId = event.roomId ?: "") // TODO update aggregation :/ or it will stay locally } - else -> { + else -> { localEchoRepository.updateSendState(event.eventId!!, event.roomId, SendState.UNDELIVERED) } } 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 60231892e1..ad47b82428 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 @@ -21,31 +21,27 @@ import androidx.lifecycle.LiveData import dagger.assisted.Assisted import dagger.assisted.AssistedFactory import dagger.assisted.AssistedInject -import org.matrix.android.sdk.api.extensions.orFalse -import org.matrix.android.sdk.api.query.QueryStringValue +import org.matrix.android.sdk.api.query.QueryStateEventValue 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.GuestAccess import org.matrix.android.sdk.api.session.room.model.RoomCanonicalAliasContent 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.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 import org.matrix.android.sdk.api.util.Optional import org.matrix.android.sdk.internal.session.content.FileUploader -import org.matrix.android.sdk.internal.session.permalinks.ViaParameterFinder -internal class DefaultStateService @AssistedInject constructor(@Assisted private val roomId: String, - private val stateEventDataSource: StateEventDataSource, - private val sendStateTask: SendStateTask, - private val fileUploader: FileUploader, - private val viaParameterFinder: ViaParameterFinder +internal class DefaultStateService @AssistedInject constructor( + @Assisted private val roomId: String, + private val stateEventDataSource: StateEventDataSource, + private val sendStateTask: SendStateTask, + private val fileUploader: FileUploader, ) : StateService { @AssistedFactory @@ -53,19 +49,19 @@ internal class DefaultStateService @AssistedInject constructor(@Assisted private fun create(roomId: String): DefaultStateService } - override fun getStateEvent(eventType: String, stateKey: QueryStringValue): Event? { + override fun getStateEvent(eventType: String, stateKey: QueryStateEventValue): Event? { return stateEventDataSource.getStateEvent(roomId, eventType, stateKey) } - override fun getStateEventLive(eventType: String, stateKey: QueryStringValue): LiveData> { + override fun getStateEventLive(eventType: String, stateKey: QueryStateEventValue): LiveData> { return stateEventDataSource.getStateEventLive(roomId, eventType, stateKey) } - override fun getStateEvents(eventTypes: Set, stateKey: QueryStringValue): List { + override fun getStateEvents(eventTypes: Set, stateKey: QueryStateEventValue): List { return stateEventDataSource.getStateEvents(roomId, eventTypes, stateKey) } - override fun getStateEventsLive(eventTypes: Set, stateKey: QueryStringValue): LiveData> { + override fun getStateEventsLive(eventTypes: Set, stateKey: QueryStateEventValue): LiveData> { return stateEventDataSource.getStateEventsLive(roomId, eventTypes, stateKey) } @@ -87,7 +83,7 @@ internal class DefaultStateService @AssistedInject constructor(@Assisted private // Safe treatment for PowerLevelContent return when (eventType) { EventType.STATE_ROOM_POWER_LEVELS -> toSafePowerLevelsContentDict() - else -> this + else -> this } } @@ -189,35 +185,4 @@ internal class DefaultStateService @AssistedInject constructor(@Assisted private } updateJoinRule(RoomJoinRules.RESTRICTED, null, allowEntries) } - - override suspend fun stopLiveLocation(userId: String) { - getLiveLocationBeaconInfo(userId, true)?.let { beaconInfoStateEvent -> - beaconInfoStateEvent.getClearContent()?.toModel()?.let { content -> - val updatedContent = content.copy(isLive = false).toContent() - - beaconInfoStateEvent.stateKey?.let { - sendStateEvent( - eventType = EventType.STATE_ROOM_BEACON_INFO.first(), - body = updatedContent, - stateKey = it - ) - } - } - } - } - - override suspend fun getLiveLocationBeaconInfo(userId: String, filterOnlyLive: Boolean): Event? { - return EventType.STATE_ROOM_BEACON_INFO - .mapNotNull { - stateEventDataSource.getStateEvent( - roomId = roomId, - eventType = it, - stateKey = QueryStringValue.Equals(userId) - ) - } - .firstOrNull { beaconInfoEvent -> - !filterOnlyLive || - beaconInfoEvent.getClearContent()?.toModel()?.isLive.orFalse() - } - } } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/state/StateEventDataSource.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/state/StateEventDataSource.kt index 42d6677409..9971ce3ccc 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/state/StateEventDataSource.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/state/StateEventDataSource.kt @@ -22,6 +22,7 @@ import com.zhuinden.monarchy.Monarchy import io.realm.Realm import io.realm.RealmQuery import io.realm.kotlin.where +import org.matrix.android.sdk.api.query.QueryStateEventValue import org.matrix.android.sdk.api.query.QueryStringValue import org.matrix.android.sdk.api.session.events.model.Event import org.matrix.android.sdk.api.util.Optional @@ -40,13 +41,13 @@ internal class StateEventDataSource @Inject constructor( private val queryStringValueProcessor: QueryStringValueProcessor ) { - fun getStateEvent(roomId: String, eventType: String, stateKey: QueryStringValue): Event? { + fun getStateEvent(roomId: String, eventType: String, stateKey: QueryStateEventValue): Event? { return realmSessionProvider.withRealm { realm -> buildStateEventQuery(realm, roomId, setOf(eventType), stateKey).findFirst()?.root?.asDomain() } } - fun getStateEventLive(roomId: String, eventType: String, stateKey: QueryStringValue): LiveData> { + fun getStateEventLive(roomId: String, eventType: String, stateKey: QueryStateEventValue): LiveData> { val liveData = monarchy.findAllMappedWithChanges( { realm -> buildStateEventQuery(realm, roomId, setOf(eventType), stateKey) }, { it.root?.asDomain() } @@ -56,7 +57,7 @@ internal class StateEventDataSource @Inject constructor( } } - fun getStateEvents(roomId: String, eventTypes: Set, stateKey: QueryStringValue): List { + fun getStateEvents(roomId: String, eventTypes: Set, stateKey: QueryStateEventValue): List { return realmSessionProvider.withRealm { realm -> buildStateEventQuery(realm, roomId, eventTypes, stateKey) .findAll() @@ -66,7 +67,7 @@ internal class StateEventDataSource @Inject constructor( } } - fun getStateEventsLive(roomId: String, eventTypes: Set, stateKey: QueryStringValue): LiveData> { + fun getStateEventsLive(roomId: String, eventTypes: Set, stateKey: QueryStateEventValue): LiveData> { val liveData = monarchy.findAllMappedWithChanges( { realm -> buildStateEventQuery(realm, roomId, eventTypes, stateKey) }, { it.root?.asDomain() } @@ -76,10 +77,11 @@ internal class StateEventDataSource @Inject constructor( } } - private fun buildStateEventQuery(realm: Realm, - roomId: String, - eventTypes: Set, - stateKey: QueryStringValue + private fun buildStateEventQuery( + realm: Realm, + roomId: String, + eventTypes: Set, + stateKey: QueryStateEventValue ): RealmQuery { return with(queryStringValueProcessor) { realm.where() @@ -89,7 +91,8 @@ internal class StateEventDataSource @Inject constructor( `in`(CurrentStateEventEntityFields.TYPE, eventTypes.toTypedArray()) } } - .process(CurrentStateEventEntityFields.STATE_KEY, stateKey) + // It's OK to cast stateKey as QueryStringValue + .process(CurrentStateEventEntityFields.STATE_KEY, stateKey as QueryStringValue) } } } 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 496bc7097f..f10479d873 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/HierarchyLiveDataHelper.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/summary/HierarchyLiveDataHelper.kt index 29db8431fd..b141dfffa7 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/summary/HierarchyLiveDataHelper.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/summary/HierarchyLiveDataHelper.kt @@ -24,7 +24,8 @@ import org.matrix.android.sdk.api.util.Optional internal class HierarchyLiveDataHelper( val spaceId: String, val memberships: List, - val roomSummaryDataSource: RoomSummaryDataSource) { + val roomSummaryDataSource: RoomSummaryDataSource +) { private val sources = HashMap>>() private val mediatorLiveData = MediatorLiveData>() 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 96e8d3c73f..cb7dc270e8 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,8 +26,9 @@ import com.zhuinden.monarchy.Monarchy import io.realm.Realm import io.realm.RealmQuery import io.realm.kotlin.where -import org.matrix.android.sdk.api.query.ActiveSpaceFilter +import org.matrix.android.sdk.api.query.QueryStringValue import org.matrix.android.sdk.api.query.RoomCategoryFilter +import org.matrix.android.sdk.api.query.SpaceFilter import org.matrix.android.sdk.api.query.isNormalized import org.matrix.android.sdk.api.session.room.ResultBoundaries import org.matrix.android.sdk.api.session.room.RoomSortOrder @@ -84,16 +85,20 @@ internal class RoomSummaryDataSource @Inject constructor( } } - fun getRoomSummaries(queryParams: RoomSummaryQueryParams, - sortOrder: RoomSortOrder = RoomSortOrder.NONE): List { + fun getRoomSummaries( + queryParams: RoomSummaryQueryParams, + sortOrder: RoomSortOrder = RoomSortOrder.NONE + ): List { return monarchy.fetchAllMappedSync( { roomSummariesQuery(it, queryParams).process(sortOrder) }, { roomSummaryMapper.map(it) } ) } - fun getRoomSummariesLive(queryParams: RoomSummaryQueryParams, - sortOrder: RoomSortOrder = RoomSortOrder.NONE): LiveData> { + fun getRoomSummariesLive( + queryParams: RoomSummaryQueryParams, + sortOrder: RoomSortOrder = RoomSortOrder.NONE + ): LiveData> { return monarchy.findAllMappedWithChanges( { roomSummariesQuery(it, queryParams).process(sortOrder) @@ -102,8 +107,10 @@ internal class RoomSummaryDataSource @Inject constructor( ) } - fun getSpaceSummariesLive(queryParams: SpaceSummaryQueryParams, - sortOrder: RoomSortOrder = RoomSortOrder.NONE): LiveData> { + fun getSpaceSummariesLive( + queryParams: SpaceSummaryQueryParams, + sortOrder: RoomSortOrder = RoomSortOrder.NONE + ): LiveData> { return getRoomSummariesLive(queryParams, sortOrder) } @@ -128,8 +135,10 @@ internal class RoomSummaryDataSource @Inject constructor( } } - fun getSpaceSummaries(spaceSummaryQueryParams: SpaceSummaryQueryParams, - sortOrder: RoomSortOrder = RoomSortOrder.NONE): List { + fun getSpaceSummaries( + spaceSummaryQueryParams: SpaceSummaryQueryParams, + sortOrder: RoomSortOrder = RoomSortOrder.NONE + ): List { return getRoomSummaries(spaceSummaryQueryParams, sortOrder) } @@ -170,9 +179,11 @@ internal class RoomSummaryDataSource @Inject constructor( .sort(RoomSummaryEntityFields.BREADCRUMBS_INDEX) } - fun getSortedPagedRoomSummariesLive(queryParams: RoomSummaryQueryParams, - pagedListConfig: PagedList.Config, - sortOrder: RoomSortOrder): LiveData> { + fun getSortedPagedRoomSummariesLive( + queryParams: RoomSummaryQueryParams, + pagedListConfig: PagedList.Config, + sortOrder: RoomSortOrder + ): LiveData> { val realmDataSourceFactory = monarchy.createDataSourceFactory { realm -> roomSummariesQuery(realm, queryParams).process(sortOrder) } @@ -185,15 +196,18 @@ internal class RoomSummaryDataSource @Inject constructor( ) } - fun getUpdatablePagedRoomSummariesLive(queryParams: RoomSummaryQueryParams, - pagedListConfig: PagedList.Config, - sortOrder: RoomSortOrder): UpdatableLivePageResult { + fun getUpdatablePagedRoomSummariesLive( + queryParams: RoomSummaryQueryParams, + pagedListConfig: PagedList.Config, + sortOrder: RoomSortOrder, + getFlattenedParents: Boolean = false + ): UpdatableLivePageResult { val realmDataSourceFactory = monarchy.createDataSourceFactory { realm -> roomSummariesQuery(realm, queryParams).process(sortOrder) } val dataSourceFactory = realmDataSourceFactory.map { roomSummaryMapper.map(it) - } + }.map { if (getFlattenedParents) it.getWithParents() else it } val boundaries = MutableLiveData(ResultBoundaries()) @@ -232,6 +246,13 @@ internal class RoomSummaryDataSource @Inject constructor( } } + private fun RoomSummary.getWithParents(): RoomSummary { + val parents = flattenParentIds.mapNotNull { parentId -> + getRoomSummary(parentId) + } + return copy(flattenParents = parents) + } + fun getCountLive(queryParams: RoomSummaryQueryParams): LiveData { val liveRooms = monarchy.findAllManagedWithChanges { roomSummariesQuery(it, queryParams) @@ -258,29 +279,13 @@ internal class RoomSummaryDataSource @Inject constructor( private fun roomSummariesQuery(realm: Realm, queryParams: RoomSummaryQueryParams): RealmQuery { val query = with(queryStringValueProcessor) { RoomSummaryEntity.where(realm) - .process(RoomSummaryEntityFields.ROOM_ID, queryParams.roomId) - .let { - if (queryParams.displayName.isNormalized()) { - it.process(RoomSummaryEntityFields.NORMALIZED_DISPLAY_NAME, queryParams.displayName) - } else { - it.process(RoomSummaryEntityFields.DISPLAY_NAME, queryParams.displayName) - } - } + .process(RoomSummaryEntityFields.ROOM_ID, QueryStringValue.IsNotEmpty) + .process(queryParams.displayName.toDisplayNameField(), queryParams.displayName) .process(RoomSummaryEntityFields.CANONICAL_ALIAS, queryParams.canonicalAlias) .process(RoomSummaryEntityFields.MEMBERSHIP_STR, queryParams.memberships) .equalTo(RoomSummaryEntityFields.IS_HIDDEN_FROM_USER, false) } - queryParams.roomCategoryFilter?.let { - when (it) { - RoomCategoryFilter.ONLY_DM -> query.equalTo(RoomSummaryEntityFields.IS_DIRECT, true) - RoomCategoryFilter.ONLY_ROOMS -> query.equalTo(RoomSummaryEntityFields.IS_DIRECT, false) - RoomCategoryFilter.ONLY_WITH_NOTIFICATIONS -> query.greaterThan(RoomSummaryEntityFields.NOTIFICATION_COUNT, 0) - RoomCategoryFilter.ALL -> { - // nop - } - } - } queryParams.roomTagQueryFilter?.let { it.isFavorite?.let { fav -> query.equalTo(RoomSummaryEntityFields.IS_FAVOURITE, fav) @@ -300,31 +305,27 @@ internal class RoomSummaryDataSource @Inject constructor( query.equalTo(RoomSummaryEntityFields.ROOM_TYPE, it) } when (queryParams.roomCategoryFilter) { - RoomCategoryFilter.ONLY_DM -> query.equalTo(RoomSummaryEntityFields.IS_DIRECT, true) - RoomCategoryFilter.ONLY_ROOMS -> query.equalTo(RoomSummaryEntityFields.IS_DIRECT, false) + RoomCategoryFilter.ONLY_DM -> query.equalTo(RoomSummaryEntityFields.IS_DIRECT, true) + RoomCategoryFilter.ONLY_ROOMS -> query.equalTo(RoomSummaryEntityFields.IS_DIRECT, false) RoomCategoryFilter.ONLY_WITH_NOTIFICATIONS -> query.greaterThan(RoomSummaryEntityFields.NOTIFICATION_COUNT, 0) - RoomCategoryFilter.ALL -> Unit // nop - null -> Unit + null -> Unit } // Timber.w("VAL: activeSpaceId : ${queryParams.activeSpaceId}") - when (queryParams.activeSpaceFilter) { - is ActiveSpaceFilter.ActiveSpace -> { + when (queryParams.spaceFilter) { + SpaceFilter.OrphanRooms -> { + // orphan rooms + query.isNull(RoomSummaryEntityFields.FLATTEN_PARENT_IDS) + } + is SpaceFilter.ActiveSpace -> { // It's annoying but for now realm java does not support querying in primitive list :/ // https://github.com/realm/realm-java/issues/5361 - if (queryParams.activeSpaceFilter.currentSpaceId == null) { - // orphan rooms - query.isNull(RoomSummaryEntityFields.FLATTEN_PARENT_IDS) - } else { - query.contains(RoomSummaryEntityFields.FLATTEN_PARENT_IDS, queryParams.activeSpaceFilter.currentSpaceId) - } + query.contains(RoomSummaryEntityFields.FLATTEN_PARENT_IDS, queryParams.spaceFilter.spaceId) } - is ActiveSpaceFilter.ExcludeSpace -> { - query.not().contains(RoomSummaryEntityFields.FLATTEN_PARENT_IDS, queryParams.activeSpaceFilter.spaceId) - } - else -> { - // nop + is SpaceFilter.ExcludeSpace -> { + query.not().contains(RoomSummaryEntityFields.FLATTEN_PARENT_IDS, queryParams.spaceFilter.spaceId) } + null -> Unit // nop } queryParams.activeGroupId?.let { activeGroupId -> @@ -333,6 +334,14 @@ internal class RoomSummaryDataSource @Inject constructor( return query } + private fun QueryStringValue.toDisplayNameField(): String { + return if (isNormalized()) { + RoomSummaryEntityFields.NORMALIZED_DISPLAY_NAME + } else { + RoomSummaryEntityFields.DISPLAY_NAME + } + } + fun getAllRoomSummaryChildOf(spaceAliasOrId: String, memberShips: List): List { val space = getSpaceSummary(spaceAliasOrId) ?: return emptyList() val result = ArrayList() @@ -427,11 +436,13 @@ internal class RoomSummaryDataSource @Inject constructor( } } - fun flattenSubSpace(current: RoomSummary, - parenting: List, - output: MutableList, - memberShips: List, - includeCurrent: Boolean = true) { + fun flattenSubSpace( + current: RoomSummary, + parenting: List, + output: MutableList, + memberShips: List, + includeCurrent: Boolean = true + ) { if (includeCurrent) { output.add(current) } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/summary/RoomSummaryUpdater.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/summary/RoomSummaryUpdater.kt index 611d6dc65e..e4afe7aa49 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/summary/RoomSummaryUpdater.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/summary/RoomSummaryUpdater.kt @@ -85,13 +85,15 @@ internal class RoomSummaryUpdater @Inject constructor( } } - fun update(realm: Realm, - roomId: String, - membership: Membership? = null, - roomSummary: RoomSyncSummary? = null, - unreadNotifications: RoomSyncUnreadNotifications? = null, - updateMembers: Boolean = false, - inviterId: String? = null) { + fun update( + realm: Realm, + roomId: String, + membership: Membership? = null, + roomSummary: RoomSyncSummary? = null, + unreadNotifications: RoomSyncUnreadNotifications? = null, + updateMembers: Boolean = false, + inviterId: String? = null + ) { val roomSummaryEntity = RoomSummaryEntity.getOrCreate(realm, roomId) if (roomSummary != null) { if (roomSummary.heroes.isNotEmpty()) { 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 fad21c0918..7795a56cbf 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 @@ -18,6 +18,7 @@ package org.matrix.android.sdk.internal.session.room.timeline import io.realm.Realm import io.realm.RealmConfiguration +import kotlinx.coroutines.CancellationException import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.SupervisorJob import kotlinx.coroutines.android.asCoroutineDispatcher @@ -235,11 +236,15 @@ internal class DefaultTimeline( val loadMoreResult = try { strategy.loadMore(count, direction, fetchOnServerIfNeeded) } catch (throwable: Throwable) { - // Timeline could not be loaded with a (likely) permanent issue, such as the - // server now knowing the initialEventId, so we want to show an error message - // and possibly restart without initialEventId. - onTimelineFailure(throwable) - return false + if (throwable is CancellationException) { + LoadMoreResult.FAILURE + } else { + // Timeline could not be loaded with a (likely) permanent issue, such as the + // server now knowing the initialEventId, so we want to show an error message + // and possibly restart without initialEventId. + onTimelineFailure(throwable) + return false + } } Timber.v("$baseLogMessage: result $loadMoreResult") val hasMoreToLoad = loadMoreResult != LoadMoreResult.REACHED_END @@ -259,8 +264,8 @@ internal class DefaultTimeline( strategy = when { rootThreadEventId != null -> buildStrategy(LoadTimelineStrategy.Mode.Thread(rootThreadEventId)) - eventId == null -> buildStrategy(LoadTimelineStrategy.Mode.Live) - else -> buildStrategy(LoadTimelineStrategy.Mode.Permalink(eventId)) + eventId == null -> buildStrategy(LoadTimelineStrategy.Mode.Live) + else -> buildStrategy(LoadTimelineStrategy.Mode.Permalink(eventId)) } rootThreadEventId?.let { @@ -344,7 +349,7 @@ internal class DefaultTimeline( private fun updateState(direction: Timeline.Direction, update: (Timeline.PaginationState) -> Timeline.PaginationState) { val stateReference = when (direction) { - Timeline.Direction.FORWARDS -> forwardState + Timeline.Direction.FORWARDS -> forwardState Timeline.Direction.BACKWARDS -> backwardState } val currentValue = stateReference.get() 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 aef9e24c8b..7c662444e4 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 @@ -61,7 +61,7 @@ internal class DefaultGetEventTask @Inject constructor( } } - event.ageLocalTs = event.unsignedData?.age?.let { clock.epochMillis() - it } + event.ageLocalTs = clock.epochMillis() - (event.unsignedData?.age ?: 0) return event } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/LiveRoomStateListener.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/LiveRoomStateListener.kt index b2692bf805..b78ad45938 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/LiveRoomStateListener.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/LiveRoomStateListener.kt @@ -46,7 +46,7 @@ internal class LiveRoomStateListener( stateEventDataSource.getStateEventsLive( roomId = roomId, eventTypes = setOf(EventType.STATE_ROOM_MEMBER), - stateKey = QueryStringValue.NoCondition, + stateKey = QueryStringValue.IsNotNull, ) } 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 e765e05578..4be97efd6c 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 @@ -36,11 +36,13 @@ import org.matrix.android.sdk.internal.database.query.where /** * This class takes care of handling case where local echo is replaced by the synced event in the db. */ -internal class LiveTimelineEvent(private val monarchy: Monarchy, - private val coroutineScope: CoroutineScope, - private val timelineEventMapper: TimelineEventMapper, - private val roomId: String, - private val eventId: String) : +internal class LiveTimelineEvent( + private val monarchy: Monarchy, + private val coroutineScope: CoroutineScope, + private val timelineEventMapper: TimelineEventMapper, + private val roomId: String, + private val eventId: String +) : MediatorLiveData>() { init { 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 4f65f85ce4..c5d4d346fd 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 @@ -267,7 +267,7 @@ internal class LoadTimelineStrategy constructor( private fun getChunkEntity(realm: Realm): RealmResults { return when (mode) { - is Mode.Live -> { + is Mode.Live -> { ChunkEntity.where(realm, roomId) .equalTo(ChunkEntityFields.IS_LAST_FORWARD, true) .findAll() @@ -275,7 +275,7 @@ internal class LoadTimelineStrategy constructor( is Mode.Permalink -> { ChunkEntity.findAllIncludingEvents(realm, listOf(mode.originEventId)) } - is Mode.Thread -> { + is Mode.Thread -> { recreateThreadChunkEntity(realm, mode.rootThreadEventId) ChunkEntity.where(realm, roomId) .equalTo(ChunkEntityFields.ROOT_THREAD_EVENT_ID, mode.rootThreadEventId) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/PaginationDirection.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/PaginationDirection.kt index 930d01759e..37092de9c4 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/PaginationDirection.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/PaginationDirection.kt @@ -31,7 +31,7 @@ internal enum class PaginationDirection(val value: String) { fun reversed(): PaginationDirection { return when (this) { - FORWARDS -> BACKWARDS + FORWARDS -> BACKWARDS BACKWARDS -> FORWARDS } } 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 6a5f9da8a6..e13f3f454f 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 @@ -70,6 +70,7 @@ internal class TimelineChunk( private val isLastForward = AtomicBoolean(chunkEntity.isLastForward) private val isLastBackward = AtomicBoolean(chunkEntity.isLastBackward) + private val nextToken = chunkEntity.nextToken private var prevChunkLatch: CompletableDeferred? = null private var nextChunkLatch: CompletableDeferred? = null @@ -136,8 +137,10 @@ internal class TimelineChunk( val prevEvents = prevChunk?.builtItems(includesNext = false, includesPrev = true).orEmpty() deepBuiltItems.addAll(prevEvents) } - - return deepBuiltItems + // In some scenario (permalink) we might end up with duplicate timeline events, so we want to be sure we only expose one. + return deepBuiltItems.distinctBy { + it.eventId + } } /** @@ -154,10 +157,6 @@ internal class TimelineChunk( val loadFromStorage = loadFromStorage(count, direction).also { logLoadedFromStorage(it, direction) } - if (loadFromStorage.numberOfEvents == 6) { - Timber.i("here") - } - val offsetCount = count - loadFromStorage.numberOfEvents return if (offsetCount == 0) { @@ -213,7 +212,7 @@ internal class TimelineChunk( fetchFromServerIfNeeded -> { fetchFromServer(offsetCount, chunkEntity.nextToken, direction) } - else -> { + else -> { LoadMoreResult.SUCCESS } } @@ -231,7 +230,7 @@ internal class TimelineChunk( fetchFromServerIfNeeded -> { fetchFromServer(offsetCount, chunkEntity.prevToken, direction) } - else -> { + else -> { LoadMoreResult.SUCCESS } } @@ -251,10 +250,6 @@ internal class TimelineChunk( } fun getBuiltEventIndex(eventId: String, searchInNext: Boolean, searchInPrev: Boolean): Int? { - val builtEventIndex = builtEventsIndexes[eventId] - if (builtEventIndex != null) { - return getOffsetIndex() + builtEventIndex - } if (searchInNext) { val nextBuiltEventIndex = nextChunk?.getBuiltEventIndex(eventId, searchInNext = true, searchInPrev = false) if (nextBuiltEventIndex != null) { @@ -267,7 +262,12 @@ internal class TimelineChunk( return prevBuiltEventIndex } } - return null + val builtEventIndex = builtEventsIndexes[eventId] + return if (builtEventIndex != null) { + getOffsetIndex() + builtEventIndex + } else { + null + } } fun getBuiltEvent(eventId: String, searchInNext: Boolean, searchInPrev: Boolean): TimelineEvent? { @@ -445,7 +445,7 @@ internal class TimelineChunk( Timber.e(failure, "Failed to fetch from server") LoadMoreResult.FAILURE } - return if (loadMoreResult == LoadMoreResult.SUCCESS) { + return if (loadMoreResult != LoadMoreResult.FAILURE) { latch?.await() loadMore(count, direction, fetchOnServerIfNeeded = false) } else { @@ -457,7 +457,7 @@ internal class TimelineChunk( return when (this) { TokenChunkEventPersistor.Result.REACHED_END -> LoadMoreResult.REACHED_END TokenChunkEventPersistor.Result.SHOULD_FETCH_MORE, - TokenChunkEventPersistor.Result.SUCCESS -> LoadMoreResult.SUCCESS + TokenChunkEventPersistor.Result.SUCCESS -> LoadMoreResult.SUCCESS } } @@ -465,16 +465,20 @@ internal class TimelineChunk( return when (this) { DefaultFetchThreadTimelineTask.Result.REACHED_END -> LoadMoreResult.REACHED_END DefaultFetchThreadTimelineTask.Result.SHOULD_FETCH_MORE, - DefaultFetchThreadTimelineTask.Result.SUCCESS -> LoadMoreResult.SUCCESS + DefaultFetchThreadTimelineTask.Result.SUCCESS -> LoadMoreResult.SUCCESS } } private fun getOffsetIndex(): Int { + if (nextToken == null) return 0 var offset = 0 var currentNextChunk = nextChunk while (currentNextChunk != null) { offset += currentNextChunk.builtEvents.size - currentNextChunk = currentNextChunk.nextChunk + currentNextChunk = currentNextChunk.nextChunk?.takeIf { + // In case of permalink we can end up with a linked nextChunk (which is the lastForward Chunk) but no nextToken + it.nextToken != null + } } return offset } @@ -486,6 +490,35 @@ internal class TimelineChunk( private fun handleDatabaseChangeSet(results: RealmResults, changeSet: OrderedCollectionChangeSet) { val insertions = changeSet.insertionRanges for (range in insertions) { + // Check if the insertion's displayIndices match our expectations - or skip this insertion. + // Inconsistencies (missing messages) can happen otherwise if we get insertions before having loaded all timeline events of the chunk. + if (builtEvents.isNotEmpty()) { + // Check consistency to item before insertions + if (range.startIndex > 0) { + val firstInsertion = results[range.startIndex]!! + val lastBeforeInsertion = builtEvents[range.startIndex - 1] + if (firstInsertion.displayIndex + 1 != lastBeforeInsertion.displayIndex) { + Timber.i( + "handleDatabaseChangeSet: skip insertion at ${range.startIndex}/${builtEvents.size}, " + + "displayIndex mismatch at ${range.startIndex}: ${firstInsertion.displayIndex} -> ${lastBeforeInsertion.displayIndex}" + ) + continue + } + } + // Check consistency to item after insertions + if (range.startIndex < builtEvents.size) { + val lastInsertion = results[range.startIndex + range.length - 1]!! + val firstAfterInsertion = builtEvents[range.startIndex] + if (firstAfterInsertion.displayIndex + 1 != lastInsertion.displayIndex) { + Timber.i( + "handleDatabaseChangeSet: skip insertion at ${range.startIndex}/${builtEvents.size}, " + + "displayIndex mismatch at ${range.startIndex + range.length}: " + + "${firstAfterInsertion.displayIndex} -> ${lastInsertion.displayIndex}" + ) + continue + } + } + } val newItems = results .subList(range.startIndex, range.startIndex + range.length) .map { it.buildAndDecryptIfNeeded() } @@ -503,8 +536,12 @@ internal class TimelineChunk( for (range in modifications) { for (modificationIndex in (range.startIndex until range.startIndex + range.length)) { val updatedEntity = results[modificationIndex] ?: continue + val displayIndex = builtEventsIndexes[updatedEntity.eventId] + if (displayIndex == null) { + continue + } try { - builtEvents[modificationIndex] = updatedEntity.buildAndDecryptIfNeeded() + builtEvents[displayIndex] = updatedEntity.buildAndDecryptIfNeeded() } catch (failure: Throwable) { Timber.v("Fail to update items at index: $modificationIndex") } 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 8b58d3ca5c..b1b9e4bb22 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 @@ -33,10 +33,12 @@ import org.matrix.android.sdk.internal.di.SessionDatabase import org.matrix.android.sdk.internal.task.TaskExecutor import javax.inject.Inject -internal class TimelineEventDataSource @Inject constructor(private val realmSessionProvider: RealmSessionProvider, - private val timelineEventMapper: TimelineEventMapper, - private val taskExecutor: TaskExecutor, - @SessionDatabase private val monarchy: Monarchy) { +internal class TimelineEventDataSource @Inject constructor( + private val realmSessionProvider: RealmSessionProvider, + private val timelineEventMapper: TimelineEventMapper, + private val taskExecutor: TaskExecutor, + @SessionDatabase private val monarchy: Monarchy +) { fun getTimelineEvent(roomId: String, eventId: String): TimelineEvent? { return realmSessionProvider.withRealm { realm -> @@ -54,7 +56,8 @@ internal class TimelineEventDataSource @Inject constructor(private val realmSess // TODO pretty bad query.. maybe we should denormalize clear type in base? return realmSessionProvider.withRealm { realm -> TimelineEventEntity.whereRoomId(realm, roomId) - .sort(TimelineEventEntityFields.DISPLAY_INDEX, Sort.ASCENDING) + .sort(TimelineEventEntityFields.ROOT.ORIGIN_SERVER_TS, Sort.ASCENDING) + .distinct(TimelineEventEntityFields.EVENT_ID) .findAll() ?.mapNotNull { timelineEventMapper.map(it).takeIf { it.root.isImageMessage() || it.root.isVideoMessage() } } .orEmpty() diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/TokenChunkEvent.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/TokenChunkEvent.kt index 465b0faac8..e9626a2173 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/TokenChunkEvent.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/TokenChunkEvent.kt @@ -24,5 +24,5 @@ internal interface TokenChunkEvent { val events: List val stateEvents: List? - fun hasMore() = start != end + fun hasMore() = end != null && start != end } 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 96ceb6c6dc..ea22f8cd78 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 @@ -33,12 +33,10 @@ import org.matrix.android.sdk.internal.database.model.ChunkEntity import org.matrix.android.sdk.internal.database.model.EventEntity import org.matrix.android.sdk.internal.database.model.EventInsertType 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 import org.matrix.android.sdk.internal.database.query.copyToRealmOrIgnore import org.matrix.android.sdk.internal.database.query.create import org.matrix.android.sdk.internal.database.query.find -import org.matrix.android.sdk.internal.database.query.findAll +import org.matrix.android.sdk.internal.database.query.findLastForwardChunkOfRoom 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 @@ -65,9 +63,11 @@ internal class TokenChunkEventPersistor @Inject constructor( SUCCESS } - suspend fun insertInDb(receivedChunk: TokenChunkEvent, - roomId: String, - direction: PaginationDirection): Result { + suspend fun insertInDb( + receivedChunk: TokenChunkEvent, + roomId: String, + direction: PaginationDirection + ): Result { monarchy .awaitTransaction { realm -> Timber.v("Start persisting ${receivedChunk.events.size} events in $roomId towards $direction") @@ -81,27 +81,22 @@ internal class TokenChunkEventPersistor @Inject constructor( nextToken = receivedChunk.start prevToken = receivedChunk.end } - val existingChunk = ChunkEntity.find(realm, roomId, prevToken = prevToken, nextToken = nextToken) if (existingChunk != null) { - Timber.v("This chunk is already in the db, checking if this might be caused by broken links") - existingChunk.fixChunkLinks(realm, roomId, direction, prevToken, nextToken) + Timber.v("This chunk is already in the db, return.") return@awaitTransaction } + + // Creates links in both directions val prevChunk = ChunkEntity.find(realm, roomId, nextToken = prevToken) val nextChunk = ChunkEntity.find(realm, roomId, prevToken = nextToken) val currentChunk = ChunkEntity.create(realm, prevToken = prevToken, nextToken = nextToken).apply { this.nextChunk = nextChunk this.prevChunk = prevChunk } - val allNextChunks = ChunkEntity.findAll(realm, roomId, prevToken = nextToken) - val allPrevChunks = ChunkEntity.findAll(realm, roomId, nextToken = prevToken) - allNextChunks?.forEach { - it.prevChunk = currentChunk - } - allPrevChunks?.forEach { - it.nextChunk = currentChunk - } + nextChunk?.prevChunk = currentChunk + prevChunk?.nextChunk = currentChunk + if (receivedChunk.events.isEmpty() && !receivedChunk.hasMore()) { handleReachEnd(roomId, direction, currentChunk) } else { @@ -120,38 +115,13 @@ internal class TokenChunkEventPersistor @Inject constructor( } } - private fun ChunkEntity.fixChunkLinks( - realm: Realm, - roomId: String, - direction: PaginationDirection, - prevToken: String?, - nextToken: String?, - ) { - if (direction == PaginationDirection.FORWARDS) { - val prevChunks = ChunkEntity.findAll(realm, roomId, nextToken = prevToken) - Timber.v("Found ${prevChunks?.size} prevChunks") - prevChunks?.forEach { - if (it.nextChunk != this) { - Timber.i("Set nextChunk for ${it.identifier()} from ${it.nextChunk?.identifier()} to ${identifier()}") - it.nextChunk = this - } - } - } else { - val nextChunks = ChunkEntity.findAll(realm, roomId, prevToken = nextToken) - Timber.v("Found ${nextChunks?.size} nextChunks") - nextChunks?.forEach { - if (it.prevChunk != this) { - Timber.i("Set prevChunk for ${it.identifier()} from ${it.prevChunk?.identifier()} to ${identifier()}") - it.prevChunk = this - } - } - } - } - private fun handleReachEnd(roomId: String, direction: PaginationDirection, currentChunk: ChunkEntity) { - Timber.v("Reach end of $roomId") + Timber.v("Reach end of $roomId in $direction") if (direction == PaginationDirection.FORWARDS) { - Timber.v("We should keep the lastForward chunk unique, the one from sync") + // We should keep the lastForward chunk unique, the one from sync, so make an unidirectional link. + // This will allow us to get live events from sync even from a permalink but won't make the link in the opposite. + val realm = currentChunk.realm + currentChunk.nextChunk = ChunkEntity.findLastForwardChunkOfRoom(realm, roomId) } else { currentChunk.isLastBackward = true } @@ -172,7 +142,7 @@ internal class TokenChunkEventPersistor @Inject constructor( val now = clock.epochMillis() stateEvents?.forEach { stateEvent -> - val ageLocalTs = stateEvent.unsignedData?.age?.let { now - it } + val ageLocalTs = now - (stateEvent.unsignedData?.age ?: 0) val stateEventEntity = stateEvent.toEntity(roomId, SendState.SYNCED, ageLocalTs).copyToRealmOrIgnore(realm, EventInsertType.PAGINATION) currentChunk.addStateEvent(roomId, stateEventEntity, direction) if (stateEvent.type == EventType.STATE_ROOM_MEMBER && stateEvent.stateKey != null) { @@ -185,38 +155,8 @@ internal class TokenChunkEventPersistor @Inject constructor( if (event.eventId == null || event.senderId == null) { return@forEach } - // We check for the timeline event with this id, but not in the thread chunk - val eventId = event.eventId - val existingTimelineEvent = TimelineEventEntity - .where(realm, roomId, eventId) - .equalTo(TimelineEventEntityFields.OWNED_BY_THREAD_CHUNK, false) - .findFirst() - // If it exists, we want to stop here, just link the prevChunk - val existingChunk = existingTimelineEvent?.chunk?.firstOrNull() - if (existingChunk != null) { - when (direction) { - PaginationDirection.BACKWARDS -> { - if (currentChunk.nextChunk == existingChunk) { - Timber.w("Avoid double link, shouldn't happen in an ideal world") - } else { - currentChunk.prevChunk = existingChunk - existingChunk.nextChunk = currentChunk - } - } - PaginationDirection.FORWARDS -> { - if (currentChunk.prevChunk == existingChunk) { - Timber.w("Avoid double link, shouldn't happen in an ideal world") - } else { - currentChunk.nextChunk = existingChunk - existingChunk.prevChunk = currentChunk - } - } - } - // Stop processing here - return@processTimelineEvents - } - val ageLocalTs = event.unsignedData?.age?.let { now - it } - var eventEntity = event.toEntity(roomId, SendState.SYNCED, ageLocalTs).copyToRealmOrIgnore(realm, EventInsertType.PAGINATION) + val ageLocalTs = now - (event.unsignedData?.age ?: 0) + val eventEntity = event.toEntity(roomId, SendState.SYNCED, ageLocalTs).copyToRealmOrIgnore(realm, EventInsertType.PAGINATION) if (event.type == EventType.STATE_ROOM_MEMBER && event.stateKey != null) { val contentToUse = if (direction == PaginationDirection.BACKWARDS) { event.prevContent 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 66bb04400b..04c72b5d6d 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 @@ -74,7 +74,7 @@ internal class UIEchoManager( when (timelineEvent.root.getClearType()) { EventType.REDACTION -> { } - EventType.REACTION -> { + EventType.REACTION -> { val content: ReactionContent? = timelineEvent.root.content?.toModel() if (RelationType.ANNOTATION == content?.relatesTo?.type) { val reaction = content.relatesTo.key diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/version/DefaultRoomVersionService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/version/DefaultRoomVersionService.kt index dc12c3209b..0bde3a11d2 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/version/DefaultRoomVersionService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/version/DefaultRoomVersionService.kt @@ -71,7 +71,7 @@ internal class DefaultRoomVersionService @AssistedInject constructor( } override fun userMayUpgradeRoom(userId: String): Boolean { - val powerLevelsHelper = stateEventDataSource.getStateEvent(roomId, EventType.STATE_ROOM_POWER_LEVELS, QueryStringValue.NoCondition) + val powerLevelsHelper = stateEventDataSource.getStateEvent(roomId, EventType.STATE_ROOM_POWER_LEVELS, QueryStringValue.IsEmpty) ?.content?.toModel() ?.let { PowerLevelsHelper(it) } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/search/DefaultSearchService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/search/DefaultSearchService.kt index 12ca36fa6b..5e33631f2c 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/search/DefaultSearchService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/search/DefaultSearchService.kt @@ -24,14 +24,16 @@ internal class DefaultSearchService @Inject constructor( private val searchTask: SearchTask ) : SearchService { - override suspend fun search(searchTerm: String, - roomId: String, - nextBatch: String?, - orderByRecent: Boolean, - limit: Int, - beforeLimit: Int, - afterLimit: Int, - includeProfile: Boolean): SearchResult { + override suspend fun search( + searchTerm: String, + roomId: String, + nextBatch: String?, + orderByRecent: Boolean, + limit: Int, + beforeLimit: Int, + afterLimit: Int, + includeProfile: Boolean + ): SearchResult { return searchTask.execute( SearchTask.Params( searchTerm = searchTerm, diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/search/SearchAPI.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/search/SearchAPI.kt index b5099e7238..4cd96c408a 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/search/SearchAPI.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/search/SearchAPI.kt @@ -30,6 +30,8 @@ internal interface SearchAPI { * Ref: https://matrix.org/docs/spec/client_server/r0.6.1#post-matrix-client-r0-search */ @POST(NetworkConstants.URI_API_PREFIX_PATH_R0 + "search") - suspend fun search(@Query("next_batch") nextBatch: String?, - @Body body: SearchRequestBody): SearchResponse + suspend fun search( + @Query("next_batch") nextBatch: String?, + @Body body: SearchRequestBody + ): SearchResponse } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/signout/DefaultSignOutService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/signout/DefaultSignOutService.kt index e7b20f905b..1bb86ecb4b 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/signout/DefaultSignOutService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/signout/DefaultSignOutService.kt @@ -21,9 +21,10 @@ import org.matrix.android.sdk.api.session.signout.SignOutService import org.matrix.android.sdk.internal.auth.SessionParamsStore import javax.inject.Inject -internal class DefaultSignOutService @Inject constructor(private val signOutTask: SignOutTask, - private val signInAgainTask: SignInAgainTask, - private val sessionParamsStore: SessionParamsStore +internal class DefaultSignOutService @Inject constructor( + private val signOutTask: SignOutTask, + private val signInAgainTask: SignInAgainTask, + private val sessionParamsStore: SessionParamsStore ) : SignOutService { override suspend fun signInAgain(password: String) { 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 178a29a5a0..32faffa670 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 @@ -44,11 +44,13 @@ internal class DefaultSpace( return spaceSummaryDataSource.getSpaceSummary(room.roomId) } - override suspend fun addChildren(roomId: String, - viaServers: List?, - order: String?, + override suspend fun addChildren( + roomId: String, + viaServers: List?, + order: String?, // autoJoin: Boolean, - suggested: Boolean?) { + suggested: Boolean? + ) { // Find best via val bestVia = viaServers ?: (spaceSummaryDataSource.getRoomSummary(roomId) 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 9320665688..d2f1b3202b 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 @@ -99,13 +99,17 @@ internal class DefaultSpaceService @Inject constructor( return spaceGetter.get(spaceId) } - override fun getSpaceSummariesLive(queryParams: SpaceSummaryQueryParams, - sortOrder: RoomSortOrder): LiveData> { + override fun getSpaceSummariesLive( + queryParams: SpaceSummaryQueryParams, + sortOrder: RoomSortOrder + ): LiveData> { return roomSummaryDataSource.getSpaceSummariesLive(queryParams, sortOrder) } - override fun getSpaceSummaries(spaceSummaryQueryParams: SpaceSummaryQueryParams, - sortOrder: RoomSortOrder): List { + override fun getSpaceSummaries( + spaceSummaryQueryParams: SpaceSummaryQueryParams, + sortOrder: RoomSortOrder + ): List { return roomSummaryDataSource.getSpaceSummaries(spaceSummaryQueryParams, sortOrder) } @@ -221,9 +225,11 @@ internal class DefaultSpaceService @Inject constructor( worldReadable = summary.isWorldReadable ) - override suspend fun joinSpace(spaceIdOrAlias: String, - reason: String?, - viaServers: List): JoinSpaceResult { + override suspend fun joinSpace( + spaceIdOrAlias: String, + reason: String?, + viaServers: List + ): JoinSpaceResult { return joinSpaceTask.execute(JoinSpaceTask.Params(spaceIdOrAlias, reason, viaServers)) } @@ -246,7 +252,7 @@ internal class DefaultSpaceService @Inject constructor( val powerLevelsEvent = stateEventDataSource.getStateEvent( roomId = parentSpaceId, eventType = EventType.STATE_ROOM_POWER_LEVELS, - stateKey = QueryStringValue.NoCondition + stateKey = QueryStringValue.IsEmpty ) val powerLevelsContent = powerLevelsEvent?.content?.toModel() ?: throw UnsupportedOperationException("Cannot add canonical child, missing powerlevel") diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/space/SpaceApi.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/space/SpaceApi.kt index 33c3c3929f..126245d015 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/space/SpaceApi.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/space/SpaceApi.kt @@ -38,7 +38,8 @@ internal interface SpaceApi { @Query("suggested_only") suggestedOnly: Boolean?, @Query("limit") limit: Int?, @Query("max_depth") maxDepth: Int?, - @Query("from") from: String?): SpacesResponse + @Query("from") from: String? + ): SpacesResponse /** * Unstable version of [getSpaceHierarchy]. @@ -49,5 +50,6 @@ internal interface SpaceApi { @Query("suggested_only") suggestedOnly: Boolean?, @Query("limit") limit: Int?, @Query("max_depth") maxDepth: Int?, - @Query("from") from: String?): SpacesResponse + @Query("from") from: String? + ): SpacesResponse } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/DefaultSyncService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/DefaultSyncService.kt new file mode 100644 index 0000000000..37869b88f9 --- /dev/null +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/DefaultSyncService.kt @@ -0,0 +1,91 @@ +/* + * 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.sync + +import androidx.lifecycle.LiveData +import org.matrix.android.sdk.api.session.sync.SyncRequestState +import org.matrix.android.sdk.api.session.sync.SyncService +import org.matrix.android.sdk.internal.di.SessionId +import org.matrix.android.sdk.internal.di.WorkManagerProvider +import org.matrix.android.sdk.internal.session.SessionState +import org.matrix.android.sdk.internal.session.sync.job.SyncThread +import org.matrix.android.sdk.internal.session.sync.job.SyncWorker +import timber.log.Timber +import javax.inject.Inject +import javax.inject.Provider + +internal class DefaultSyncService @Inject constructor( + @SessionId val sessionId: String, + private val workManagerProvider: WorkManagerProvider, + private val syncThreadProvider: Provider, + private val syncTokenStore: SyncTokenStore, + private val syncRequestStateTracker: SyncRequestStateTracker, + private val sessionState: SessionState, +) : SyncService { + private var syncThread: SyncThread? = null + + override fun requireBackgroundSync() { + SyncWorker.requireBackgroundSync(workManagerProvider, sessionId) + } + + override fun startAutomaticBackgroundSync(timeOutInSeconds: Long, repeatDelayInSeconds: Long) { + SyncWorker.automaticallyBackgroundSync(workManagerProvider, sessionId, timeOutInSeconds, repeatDelayInSeconds) + } + + override fun stopAnyBackgroundSync() { + SyncWorker.stopAnyBackgroundSync(workManagerProvider) + } + + override fun startSync(fromForeground: Boolean) { + Timber.i("Starting sync thread") + assert(sessionState.isOpen) + val localSyncThread = getSyncThread() + localSyncThread.setInitialForeground(fromForeground) + if (!localSyncThread.isAlive) { + localSyncThread.start() + } else { + localSyncThread.restart() + Timber.w("Attempt to start an already started thread") + } + } + + override fun stopSync() { + assert(sessionState.isOpen) + syncThread?.kill() + syncThread = null + } + + override fun getSyncStateLive() = getSyncThread().liveState() + + override fun syncFlow() = getSyncThread().syncFlow() + + override fun getSyncState() = getSyncThread().currentState() + + override fun getSyncRequestStateLive(): LiveData { + return syncRequestStateTracker.syncRequestState + } + + override fun hasAlreadySynced(): Boolean { + return syncTokenStore.getLastToken() != null + } + + private fun getSyncThread(): SyncThread { + return syncThread ?: syncThreadProvider.get().also { + syncThread = it + } + } +} diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/Extensions.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/Extensions.kt new file mode 100644 index 0000000000..ae542103b7 --- /dev/null +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/Extensions.kt @@ -0,0 +1,49 @@ +/* + * 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.session.sync + +import org.matrix.android.sdk.api.session.sync.InitialSyncStep + +internal inline fun reportSubtask( + reporter: ProgressReporter?, + initialSyncStep: InitialSyncStep, + totalProgress: Int, + parentWeight: Float, + block: () -> T +): T { + reporter?.startTask(initialSyncStep, totalProgress, parentWeight) + return block().also { + reporter?.endTask() + } +} + +internal inline fun Map.mapWithProgress( + reporter: ProgressReporter?, + initialSyncStep: InitialSyncStep, + parentWeight: Float, + transform: (Map.Entry) -> R +): List { + var current = 0F + reporter?.startTask(initialSyncStep, count() + 1, parentWeight) + return map { + reporter?.reportProgress(current) + current++ + transform.invoke(it) + }.also { + reporter?.endTask() + } +} diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/initsync/ProgressReporter.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/ProgressReporter.kt similarity index 74% rename from matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/initsync/ProgressReporter.kt rename to matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/ProgressReporter.kt index 8a7b26b4b8..6d77b9be5e 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/initsync/ProgressReporter.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/ProgressReporter.kt @@ -14,14 +14,16 @@ * limitations under the License. */ -package org.matrix.android.sdk.internal.session.initsync +package org.matrix.android.sdk.internal.session.sync -import org.matrix.android.sdk.api.session.initsync.InitSyncStep +import org.matrix.android.sdk.api.session.sync.InitialSyncStep internal interface ProgressReporter { - fun startTask(initSyncStep: InitSyncStep, - totalProgress: Int, - parentWeight: Float) + fun startTask( + initialSyncStep: InitialSyncStep, + totalProgress: Int, + parentWeight: Float + ) fun reportProgress(progress: Float) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/SyncAPI.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/SyncAPI.kt index 6c8a71f35e..2a2fd6ab1d 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/SyncAPI.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/SyncAPI.kt @@ -31,10 +31,11 @@ internal interface SyncAPI { * Set all the timeouts to 1 minute by default. */ @GET(NetworkConstants.URI_API_PREFIX_PATH_R0 + "sync") - suspend fun sync(@QueryMap params: Map, - @Header(TimeOutInterceptor.CONNECT_TIMEOUT) connectTimeOut: Long = TimeOutInterceptor.DEFAULT_LONG_TIMEOUT, - @Header(TimeOutInterceptor.READ_TIMEOUT) readTimeOut: Long = TimeOutInterceptor.DEFAULT_LONG_TIMEOUT, - @Header(TimeOutInterceptor.WRITE_TIMEOUT) writeTimeOut: Long = TimeOutInterceptor.DEFAULT_LONG_TIMEOUT + suspend fun sync( + @QueryMap params: Map, + @Header(TimeOutInterceptor.CONNECT_TIMEOUT) connectTimeOut: Long = TimeOutInterceptor.DEFAULT_LONG_TIMEOUT, + @Header(TimeOutInterceptor.READ_TIMEOUT) readTimeOut: Long = TimeOutInterceptor.DEFAULT_LONG_TIMEOUT, + @Header(TimeOutInterceptor.WRITE_TIMEOUT) writeTimeOut: Long = TimeOutInterceptor.DEFAULT_LONG_TIMEOUT ): SyncResponse /** @@ -42,9 +43,10 @@ internal interface SyncAPI { */ @Streaming @GET(NetworkConstants.URI_API_PREFIX_PATH_R0 + "sync") - fun syncStream(@QueryMap params: Map, - @Header(TimeOutInterceptor.CONNECT_TIMEOUT) connectTimeOut: Long = TimeOutInterceptor.DEFAULT_LONG_TIMEOUT, - @Header(TimeOutInterceptor.READ_TIMEOUT) readTimeOut: Long = TimeOutInterceptor.DEFAULT_LONG_TIMEOUT, - @Header(TimeOutInterceptor.WRITE_TIMEOUT) writeTimeOut: Long = TimeOutInterceptor.DEFAULT_LONG_TIMEOUT + fun syncStream( + @QueryMap params: Map, + @Header(TimeOutInterceptor.CONNECT_TIMEOUT) connectTimeOut: Long = TimeOutInterceptor.DEFAULT_LONG_TIMEOUT, + @Header(TimeOutInterceptor.READ_TIMEOUT) readTimeOut: Long = TimeOutInterceptor.DEFAULT_LONG_TIMEOUT, + @Header(TimeOutInterceptor.WRITE_TIMEOUT) writeTimeOut: Long = TimeOutInterceptor.DEFAULT_LONG_TIMEOUT ): Call } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/SyncModule.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/SyncModule.kt index 4b31dc4d9b..d78070439c 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/SyncModule.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/SyncModule.kt @@ -19,6 +19,7 @@ package org.matrix.android.sdk.internal.session.sync import dagger.Binds import dagger.Module import dagger.Provides +import org.matrix.android.sdk.api.session.sync.SyncService import org.matrix.android.sdk.internal.session.SessionScope import retrofit2.Retrofit @@ -35,6 +36,9 @@ internal abstract class SyncModule { } } + @Binds + abstract fun bindSyncService(service: DefaultSyncService): SyncService + @Binds abstract fun bindSyncTask(task: DefaultSyncTask): SyncTask 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 ce41a4568c..9df6d52ad3 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 @@ -35,9 +35,9 @@ internal enum class SyncPresence(val value: String) { companion object { fun from(presenceEnum: PresenceEnum): SyncPresence { return when (presenceEnum) { - PresenceEnum.ONLINE -> Online - PresenceEnum.OFFLINE -> Offline - PresenceEnum.BUSY -> Busy + PresenceEnum.ONLINE -> Online + PresenceEnum.OFFLINE -> Offline + PresenceEnum.BUSY -> Busy PresenceEnum.UNAVAILABLE -> Unavailable } } 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/sync/SyncRequestStateTracker.kt similarity index 63% rename from matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/initsync/DefaultSyncStatusService.kt rename to matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/SyncRequestStateTracker.kt index 2aa0be80c9..bcc5fcf9ab 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/sync/SyncRequestStateTracker.kt @@ -13,52 +13,51 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.matrix.android.sdk.internal.session.initsync -import androidx.lifecycle.LiveData +package org.matrix.android.sdk.internal.session.sync + import androidx.lifecycle.MutableLiveData -import org.matrix.android.sdk.api.session.initsync.InitSyncStep -import org.matrix.android.sdk.api.session.initsync.SyncStatusService +import org.matrix.android.sdk.api.session.sync.InitialSyncStep +import org.matrix.android.sdk.api.session.sync.SyncRequestState import org.matrix.android.sdk.internal.session.SessionScope import javax.inject.Inject @SessionScope -internal class DefaultSyncStatusService @Inject constructor() : - SyncStatusService, +internal class SyncRequestStateTracker @Inject constructor() : ProgressReporter { - private val status = MutableLiveData() + val syncRequestState = MutableLiveData() private var rootTask: TaskInfo? = null - override fun getSyncStatusLive(): LiveData { - return status - } - // Only to be used for incremental sync - fun setStatus(newStatus: SyncStatusService.Status.IncrementalSyncStatus) { - status.postValue(newStatus) + fun setSyncRequestState(newSyncRequestState: SyncRequestState.IncrementalSyncRequestState) { + syncRequestState.postValue(newSyncRequestState) } /** * Create a rootTask. */ - fun startRoot(initSyncStep: InitSyncStep, - totalProgress: Int) { + fun startRoot( + initialSyncStep: InitialSyncStep, + totalProgress: Int + ) { endAll() - rootTask = TaskInfo(initSyncStep, totalProgress, null, 1F) + rootTask = TaskInfo(initialSyncStep, totalProgress, null, 1F) reportProgress(0F) } /** * Add a child to the leaf. */ - override fun startTask(initSyncStep: InitSyncStep, - totalProgress: Int, - parentWeight: Float) { + override fun startTask( + initialSyncStep: InitialSyncStep, + totalProgress: Int, + parentWeight: Float + ) { val currentLeaf = rootTask?.leaf() ?: return currentLeaf.child = TaskInfo( - initSyncStep = initSyncStep, + initialSyncStep = initialSyncStep, totalProgress = totalProgress, parent = currentLeaf, parentWeight = parentWeight @@ -72,7 +71,7 @@ internal class DefaultSyncStatusService @Inject constructor() : // Update the progress of the leaf and all its parents leaf.setProgress(progress) // Then update the live data using leaf wording and root progress - status.postValue(SyncStatusService.Status.InitialSyncProgressing(leaf.initSyncStep, root.currentProgress.toInt())) + syncRequestState.postValue(SyncRequestState.InitialSyncProgressing(leaf.initialSyncStep, root.currentProgress.toInt())) } } } @@ -87,13 +86,13 @@ internal class DefaultSyncStatusService @Inject constructor() : // And close it endedTask.parent.child = null } else { - status.postValue(SyncStatusService.Status.Idle) + syncRequestState.postValue(SyncRequestState.Idle) } } } fun endAll() { rootTask = null - status.postValue(SyncStatusService.Status.Idle) + syncRequestState.postValue(SyncRequestState.Idle) } } 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 02a7a9a37f..9e5302222a 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.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.InitialSyncStep 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 @@ -32,8 +32,6 @@ import org.matrix.android.sdk.internal.di.WorkManagerProvider import org.matrix.android.sdk.internal.session.SessionListeners 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.pushrules.ProcessEventForPushTask import org.matrix.android.sdk.internal.session.sync.handler.CryptoSyncHandler import org.matrix.android.sdk.internal.session.sync.handler.GroupSyncHandler @@ -68,9 +66,11 @@ internal class SyncResponseHandler @Inject constructor( private val presenceSyncHandler: PresenceSyncHandler ) { - suspend fun handleResponse(syncResponse: SyncResponse, - fromToken: String?, - reporter: ProgressReporter?) { + suspend fun handleResponse( + syncResponse: SyncResponse, + fromToken: String?, + reporter: ProgressReporter? + ) { val isInitialSync = fromToken == null Timber.v("Start handling sync, is InitialSync: $isInitialSync") @@ -88,7 +88,7 @@ internal class SyncResponseHandler @Inject constructor( // to ensure to decrypt them properly measureTimeMillis { Timber.v("Handle toDevice") - reportSubtask(reporter, InitSyncStep.ImportingAccountCrypto, 100, 0.1f) { + reportSubtask(reporter, InitialSyncStep.ImportingAccountCrypto, 100, 0.1f) { if (syncResponse.toDevice != null) { cryptoSyncHandler.handleToDevice(syncResponse.toDevice, reporter) } @@ -109,7 +109,7 @@ internal class SyncResponseHandler @Inject constructor( // IMPORTANT nothing should be suspend here as we are accessing the realm instance (thread local) measureTimeMillis { Timber.v("Handle rooms") - reportSubtask(reporter, InitSyncStep.ImportingAccountRoom, 1, 0.7f) { + reportSubtask(reporter, InitialSyncStep.ImportingAccountRoom, 1, 0.7f) { if (syncResponse.rooms != null) { roomSyncHandler.handle(realm, syncResponse.rooms, isInitialSync, aggregator, reporter) } @@ -119,7 +119,7 @@ internal class SyncResponseHandler @Inject constructor( } measureTimeMillis { - reportSubtask(reporter, InitSyncStep.ImportingAccountGroups, 1, 0.1f) { + reportSubtask(reporter, InitialSyncStep.ImportingAccountGroups, 1, 0.1f) { Timber.v("Handle groups") if (syncResponse.groups != null) { groupSyncHandler.handle(realm, syncResponse.groups, reporter) @@ -130,7 +130,7 @@ internal class SyncResponseHandler @Inject constructor( } measureTimeMillis { - reportSubtask(reporter, InitSyncStep.ImportingAccountData, 1, 0.1f) { + reportSubtask(reporter, InitialSyncStep.ImportingAccountData, 1, 0.1f) { Timber.v("Handle accountData") userAccountDataSyncHandler.handle(realm, syncResponse.accountData) } 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 f88d973101..ea296d379d 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 @@ -21,10 +21,10 @@ import okhttp3.ResponseBody 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.initsync.InitSyncStep -import org.matrix.android.sdk.api.session.initsync.SyncStatusService import org.matrix.android.sdk.api.session.statistics.StatisticEvent +import org.matrix.android.sdk.api.session.sync.InitialSyncStep import org.matrix.android.sdk.api.session.sync.InitialSyncStrategy +import org.matrix.android.sdk.api.session.sync.SyncRequestState import org.matrix.android.sdk.api.session.sync.initialSyncStrategy import org.matrix.android.sdk.api.session.sync.model.LazyRoomSyncEphemeral import org.matrix.android.sdk.api.session.sync.model.SyncResponse @@ -38,8 +38,6 @@ import org.matrix.android.sdk.internal.session.SessionListeners import org.matrix.android.sdk.internal.session.dispatchTo import org.matrix.android.sdk.internal.session.filter.FilterRepository import org.matrix.android.sdk.internal.session.homeserver.GetHomeServerCapabilitiesTask -import org.matrix.android.sdk.internal.session.initsync.DefaultSyncStatusService -import org.matrix.android.sdk.internal.session.initsync.reportSubtask import org.matrix.android.sdk.internal.session.sync.parsing.InitialSyncResponseParser import org.matrix.android.sdk.internal.session.user.UserStore import org.matrix.android.sdk.internal.task.Task @@ -68,7 +66,7 @@ internal class DefaultSyncTask @Inject constructor( @UserId private val userId: String, private val filterRepository: FilterRepository, private val syncResponseHandler: SyncResponseHandler, - private val defaultSyncStatusService: DefaultSyncStatusService, + private val syncRequestStateTracker: SyncRequestStateTracker, private val syncTokenStore: SyncTokenStore, private val getHomeServerCapabilitiesTask: GetHomeServerCapabilitiesTask, private val userStore: UserStore, @@ -115,7 +113,7 @@ internal class DefaultSyncTask @Inject constructor( displayName = user?.displayName, avatarUrl = user?.avatarUrl ) - defaultSyncStatusService.startRoot(InitSyncStep.ImportingAccount, 100) + syncRequestStateTracker.startRoot(InitialSyncStep.ImportingAccount, 100) } // Maybe refresh the homeserver capabilities data we know getHomeServerCapabilitiesTask.execute(GetHomeServerCapabilitiesTask.Params(forceRefresh = false)) @@ -132,7 +130,7 @@ internal class DefaultSyncTask @Inject constructor( roomSyncEphemeralTemporaryStore.reset() workingDir.mkdirs() val file = downloadInitSyncResponse(requestParams, syncStatisticsData) - syncResponseToReturn = reportSubtask(defaultSyncStatusService, InitSyncStep.ImportingAccount, 1, 0.7F) { + syncResponseToReturn = reportSubtask(syncRequestStateTracker, InitialSyncStep.ImportingAccount, 1, 0.7F) { handleSyncFile(file, initSyncStrategy) } // Delete all files @@ -150,15 +148,15 @@ internal class DefaultSyncTask @Inject constructor( syncStatisticsData.requestInitSyncTime = SystemClock.elapsedRealtime() syncStatisticsData.downloadInitSyncTime = syncStatisticsData.requestInitSyncTime logDuration("INIT_SYNC Database insertion", loggerTag, clock) { - syncResponseHandler.handleResponse(syncResponse, token, defaultSyncStatusService) + syncResponseHandler.handleResponse(syncResponse, token, syncRequestStateTracker) } syncResponseToReturn = syncResponse } } - defaultSyncStatusService.endAll() + syncRequestStateTracker.endAll() } else { Timber.tag(loggerTag.value).d("Start incremental sync request with since token $token") - defaultSyncStatusService.setStatus(SyncStatusService.Status.IncrementalSyncIdle) + syncRequestStateTracker.setSyncRequestState(SyncRequestState.IncrementalSyncIdle) val syncResponse = try { executeRequest(globalErrorReceiver) { syncAPI.sync( @@ -168,7 +166,7 @@ internal class DefaultSyncTask @Inject constructor( } } catch (throwable: Throwable) { Timber.tag(loggerTag.value).e(throwable, "Incremental sync request error") - defaultSyncStatusService.setStatus(SyncStatusService.Status.IncrementalSyncError) + syncRequestStateTracker.setSyncRequestState(SyncRequestState.IncrementalSyncError) throw throwable } val nbRooms = syncResponse.rooms?.invite.orEmpty().size + syncResponse.rooms?.join.orEmpty().size + syncResponse.rooms?.leave.orEmpty().size @@ -177,8 +175,8 @@ internal class DefaultSyncTask @Inject constructor( Timber.tag(loggerTag.value).d( "Incremental sync request parsing, $nbRooms room(s) $nbToDevice toDevice(s). Got nextBatch: $nextBatch" ) - defaultSyncStatusService.setStatus( - SyncStatusService.Status.IncrementalSyncParsing( + syncRequestStateTracker.setSyncRequestState( + SyncRequestState.IncrementalSyncParsing( rooms = nbRooms, toDevice = nbToDevice ) @@ -186,7 +184,7 @@ internal class DefaultSyncTask @Inject constructor( syncResponseHandler.handleResponse(syncResponse, token, null) syncResponseToReturn = syncResponse Timber.tag(loggerTag.value).d("Incremental sync done") - defaultSyncStatusService.setStatus(SyncStatusService.Status.IncrementalSyncDone) + syncRequestStateTracker.setSyncRequestState(SyncRequestState.IncrementalSyncDone) } syncStatisticsData.treatmentSyncTime = SystemClock.elapsedRealtime() syncStatisticsData.nbOfRooms = syncResponseToReturn?.rooms?.join?.size ?: 0 @@ -201,20 +199,20 @@ internal class DefaultSyncTask @Inject constructor( val status = initialSyncStatusRepository.getStep() if (workingFile.exists() && status >= InitialSyncStatus.STEP_DOWNLOADED) { Timber.tag(loggerTag.value).d("INIT_SYNC file is already here") - reportSubtask(defaultSyncStatusService, InitSyncStep.Downloading, 1, 0.3f) { + reportSubtask(syncRequestStateTracker, InitialSyncStep.Downloading, 1, 0.3f) { // Empty task } } else { initialSyncStatusRepository.setStep(InitialSyncStatus.STEP_DOWNLOADING) val syncResponse = logDuration("INIT_SYNC Perform server request", loggerTag, clock) { - reportSubtask(defaultSyncStatusService, InitSyncStep.ServerComputing, 1, 0.2f) { + reportSubtask(syncRequestStateTracker, InitialSyncStep.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, clock) { - reportSubtask(defaultSyncStatusService, InitSyncStep.Downloading, 1, 0.1f) { + reportSubtask(syncRequestStateTracker, InitialSyncStep.Downloading, 1, 0.1f) { syncResponse.body()?.byteStream()?.use { inputStream -> workingFile.outputStream().use { outputStream -> inputStream.copyTo(outputStream) @@ -263,7 +261,7 @@ internal class DefaultSyncTask @Inject constructor( Timber.tag(loggerTag.value).d("INIT_SYNC $nbOfJoinedRooms rooms, $nbOfJoinedRoomsInFile ephemeral stored into files") logDuration("INIT_SYNC Database insertion", loggerTag, clock) { - syncResponseHandler.handleResponse(syncResponse, null, defaultSyncStatusService) + syncResponseHandler.handleResponse(syncResponse, null, syncRequestStateTracker) } initialSyncStatusRepository.setStep(InitialSyncStatus.STEP_SUCCESS) syncResponse diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/initsync/TaskInfo.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/TaskInfo.kt similarity index 81% rename from matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/initsync/TaskInfo.kt rename to matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/TaskInfo.kt index fef16bf1d7..7032aae2b2 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/initsync/TaskInfo.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/TaskInfo.kt @@ -14,15 +14,17 @@ * limitations under the License. */ -package org.matrix.android.sdk.internal.session.initsync +package org.matrix.android.sdk.internal.session.sync -import org.matrix.android.sdk.api.session.initsync.InitSyncStep +import org.matrix.android.sdk.api.session.sync.InitialSyncStep import timber.log.Timber -internal class TaskInfo(val initSyncStep: InitSyncStep, - val totalProgress: Int, - val parent: TaskInfo?, - val parentWeight: Float) { +internal class TaskInfo( + val initialSyncStep: InitialSyncStep, + val totalProgress: Int, + val parent: TaskInfo?, + val parentWeight: Float +) { var child: TaskInfo? = null var currentProgress = 0F private set diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/handler/CryptoSyncHandler.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/handler/CryptoSyncHandler.kt index e5a5a0bbad..b6142b3a7a 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/handler/CryptoSyncHandler.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/handler/CryptoSyncHandler.kt @@ -29,14 +29,16 @@ import org.matrix.android.sdk.api.session.sync.model.SyncResponse import org.matrix.android.sdk.api.session.sync.model.ToDeviceSyncResponse import org.matrix.android.sdk.internal.crypto.DefaultCryptoService import org.matrix.android.sdk.internal.crypto.verification.DefaultVerificationService -import org.matrix.android.sdk.internal.session.initsync.ProgressReporter +import org.matrix.android.sdk.internal.session.sync.ProgressReporter import timber.log.Timber import javax.inject.Inject private val loggerTag = LoggerTag("CryptoSyncHandler", LoggerTag.CRYPTO) -internal class CryptoSyncHandler @Inject constructor(private val cryptoService: DefaultCryptoService, - private val verificationService: DefaultVerificationService) { +internal class CryptoSyncHandler @Inject constructor( + private val cryptoService: DefaultCryptoService, + private val verificationService: DefaultVerificationService +) { suspend fun handleToDevice(toDevice: ToDeviceSyncResponse, progressReporter: ProgressReporter? = null) { val total = toDevice.events?.size ?: 0 diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/handler/GroupSyncHandler.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/handler/GroupSyncHandler.kt index 552462e25e..1983d9f433 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/handler/GroupSyncHandler.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/handler/GroupSyncHandler.kt @@ -17,16 +17,16 @@ package org.matrix.android.sdk.internal.session.sync.handler import io.realm.Realm -import org.matrix.android.sdk.api.session.initsync.InitSyncStep import org.matrix.android.sdk.api.session.room.model.Membership +import org.matrix.android.sdk.api.session.sync.InitialSyncStep import org.matrix.android.sdk.api.session.sync.model.GroupsSyncResponse import org.matrix.android.sdk.api.session.sync.model.InvitedGroupSync import org.matrix.android.sdk.internal.database.model.GroupEntity import org.matrix.android.sdk.internal.database.model.GroupSummaryEntity import org.matrix.android.sdk.internal.database.query.getOrCreate import org.matrix.android.sdk.internal.database.query.where -import org.matrix.android.sdk.internal.session.initsync.ProgressReporter -import org.matrix.android.sdk.internal.session.initsync.mapWithProgress +import org.matrix.android.sdk.internal.session.sync.ProgressReporter +import org.matrix.android.sdk.internal.session.sync.mapWithProgress import javax.inject.Inject internal class GroupSyncHandler @Inject constructor() { @@ -37,9 +37,11 @@ internal class GroupSyncHandler @Inject constructor() { data class LEFT(val data: Map) : HandlingStrategy() } - fun handle(realm: Realm, - roomsSyncResponse: GroupsSyncResponse, - reporter: ProgressReporter? = null) { + fun handle( + realm: Realm, + roomsSyncResponse: GroupsSyncResponse, + reporter: ProgressReporter? = null + ) { handleGroupSync(realm, HandlingStrategy.JOINED(roomsSyncResponse.join), reporter) handleGroupSync(realm, HandlingStrategy.INVITED(roomsSyncResponse.invite), reporter) handleGroupSync(realm, HandlingStrategy.LEFT(roomsSyncResponse.leave), reporter) @@ -49,26 +51,28 @@ internal class GroupSyncHandler @Inject constructor() { private fun handleGroupSync(realm: Realm, handlingStrategy: HandlingStrategy, reporter: ProgressReporter?) { val groups = when (handlingStrategy) { - is HandlingStrategy.JOINED -> - handlingStrategy.data.mapWithProgress(reporter, InitSyncStep.ImportingAccountGroups, 0.6f) { + is HandlingStrategy.JOINED -> + handlingStrategy.data.mapWithProgress(reporter, InitialSyncStep.ImportingAccountGroups, 0.6f) { handleJoinedGroup(realm, it.key) } is HandlingStrategy.INVITED -> - handlingStrategy.data.mapWithProgress(reporter, InitSyncStep.ImportingAccountGroups, 0.3f) { + handlingStrategy.data.mapWithProgress(reporter, InitialSyncStep.ImportingAccountGroups, 0.3f) { handleInvitedGroup(realm, it.key) } - is HandlingStrategy.LEFT -> - handlingStrategy.data.mapWithProgress(reporter, InitSyncStep.ImportingAccountGroups, 0.1f) { + is HandlingStrategy.LEFT -> + handlingStrategy.data.mapWithProgress(reporter, InitialSyncStep.ImportingAccountGroups, 0.1f) { handleLeftGroup(realm, it.key) } } realm.insertOrUpdate(groups) } - private fun handleJoinedGroup(realm: Realm, - groupId: String): GroupEntity { + private fun handleJoinedGroup( + realm: Realm, + groupId: String + ): GroupEntity { val groupEntity = GroupEntity.where(realm, groupId).findFirst() ?: GroupEntity(groupId) val groupSummaryEntity = GroupSummaryEntity.getOrCreate(realm, groupId) groupEntity.membership = Membership.JOIN @@ -76,8 +80,10 @@ internal class GroupSyncHandler @Inject constructor() { return groupEntity } - private fun handleInvitedGroup(realm: Realm, - groupId: String): GroupEntity { + private fun handleInvitedGroup( + realm: Realm, + groupId: String + ): GroupEntity { val groupEntity = GroupEntity.where(realm, groupId).findFirst() ?: GroupEntity(groupId) val groupSummaryEntity = GroupSummaryEntity.getOrCreate(realm, groupId) groupEntity.membership = Membership.INVITE @@ -85,8 +91,10 @@ internal class GroupSyncHandler @Inject constructor() { return groupEntity } - private fun handleLeftGroup(realm: Realm, - groupId: String): GroupEntity { + private fun handleLeftGroup( + realm: Realm, + groupId: String + ): GroupEntity { val groupEntity = GroupEntity.where(realm, groupId).findFirst() ?: GroupEntity(groupId) val groupSummaryEntity = GroupSummaryEntity.getOrCreate(realm, groupId) groupEntity.membership = Membership.LEAVE 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 c213ea4bcf..0f296ded5d 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 @@ -84,10 +84,10 @@ internal class UserAccountDataSyncHandler @Inject constructor( // Generic handling, just save in base handleGenericAccountData(realm, event.type, event.content) when (event.type) { - UserAccountDataTypes.TYPE_DIRECT_MESSAGES -> handleDirectChatRooms(realm, event) - UserAccountDataTypes.TYPE_PUSH_RULES -> handlePushRules(realm, event) + UserAccountDataTypes.TYPE_DIRECT_MESSAGES -> handleDirectChatRooms(realm, event) + UserAccountDataTypes.TYPE_PUSH_RULES -> handlePushRules(realm, event) UserAccountDataTypes.TYPE_IGNORED_USER_LIST -> handleIgnoredUsers(realm, event) - UserAccountDataTypes.TYPE_BREADCRUMBS -> handleBreadcrumbs(realm, event) + UserAccountDataTypes.TYPE_BREADCRUMBS -> handleBreadcrumbs(realm, event) } } } 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 77bee18df9..7329611a01 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,9 +44,11 @@ internal class ReadReceiptHandler @Inject constructor( companion object { - fun createContent(userId: String, - eventId: String, - currentTimeMillis: Long): ReadReceiptContent { + fun createContent( + userId: String, + eventId: String, + currentTimeMillis: Long + ): ReadReceiptContent { return mapOf( eventId to mapOf( READ_KEY to mapOf( @@ -59,11 +61,13 @@ internal class ReadReceiptHandler @Inject constructor( } } - fun handle(realm: Realm, - roomId: String, - content: ReadReceiptContent?, - isInitialSync: Boolean, - aggregator: SyncResponsePostTreatmentAggregator?) { + fun handle( + realm: Realm, + roomId: String, + content: ReadReceiptContent?, + isInitialSync: Boolean, + aggregator: SyncResponsePostTreatmentAggregator? + ) { content ?: return try { @@ -73,11 +77,13 @@ internal class ReadReceiptHandler @Inject constructor( } } - private fun handleReadReceiptContent(realm: Realm, - roomId: String, - content: ReadReceiptContent, - isInitialSync: Boolean, - aggregator: SyncResponsePostTreatmentAggregator?) { + private fun handleReadReceiptContent( + realm: Realm, + roomId: String, + content: ReadReceiptContent, + isInitialSync: Boolean, + aggregator: SyncResponsePostTreatmentAggregator? + ) { if (isInitialSync) { initialSyncStrategy(realm, roomId, content) } else { @@ -101,10 +107,12 @@ internal class ReadReceiptHandler @Inject constructor( realm.insertOrUpdate(readReceiptSummaries) } - private fun incrementalSyncStrategy(realm: Realm, - roomId: String, - content: ReadReceiptContent, - aggregator: SyncResponsePostTreatmentAggregator?) { + private fun incrementalSyncStrategy( + realm: Realm, + roomId: String, + content: ReadReceiptContent, + aggregator: SyncResponsePostTreatmentAggregator? + ) { // First check if we have data from init sync to handle getContentFromInitSync(roomId)?.let { Timber.w("INIT_SYNC Insert during incremental sync RR for room $roomId") 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 c5d14afac0..30e1ec6679 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 @@ -27,11 +27,11 @@ 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.homeserver.HomeServerCapabilitiesService -import org.matrix.android.sdk.api.session.initsync.InitSyncStep 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.send.SendState import org.matrix.android.sdk.api.session.room.threads.model.ThreadSummaryUpdateType +import org.matrix.android.sdk.api.session.sync.InitialSyncStep import org.matrix.android.sdk.api.session.sync.InitialSyncStrategy import org.matrix.android.sdk.api.session.sync.initialSyncStrategy import org.matrix.android.sdk.api.session.sync.model.InvitedRoomSync @@ -57,6 +57,7 @@ import org.matrix.android.sdk.internal.database.model.deleteOnCascade import org.matrix.android.sdk.internal.database.model.threads.ThreadSummaryEntity import org.matrix.android.sdk.internal.database.query.copyToRealmOrIgnore import org.matrix.android.sdk.internal.database.query.find +import org.matrix.android.sdk.internal.database.query.findAll import org.matrix.android.sdk.internal.database.query.findLastForwardChunkOfRoom import org.matrix.android.sdk.internal.database.query.findLastForwardChunkOfThread import org.matrix.android.sdk.internal.database.query.getOrCreate @@ -67,17 +68,17 @@ import org.matrix.android.sdk.internal.di.UserId import org.matrix.android.sdk.internal.extensions.clearWith import org.matrix.android.sdk.internal.session.StreamEventsManager import org.matrix.android.sdk.internal.session.events.getFixedRoomMemberContent -import org.matrix.android.sdk.internal.session.initsync.ProgressReporter -import org.matrix.android.sdk.internal.session.initsync.mapWithProgress -import org.matrix.android.sdk.internal.session.initsync.reportSubtask import org.matrix.android.sdk.internal.session.room.membership.RoomChangeMembershipStateDataSource import org.matrix.android.sdk.internal.session.room.membership.RoomMemberEventHandler import org.matrix.android.sdk.internal.session.room.summary.RoomSummaryUpdater import org.matrix.android.sdk.internal.session.room.timeline.PaginationDirection import org.matrix.android.sdk.internal.session.room.timeline.TimelineInput import org.matrix.android.sdk.internal.session.room.typing.TypingEventContent +import org.matrix.android.sdk.internal.session.sync.ProgressReporter import org.matrix.android.sdk.internal.session.sync.SyncResponsePostTreatmentAggregator +import org.matrix.android.sdk.internal.session.sync.mapWithProgress import org.matrix.android.sdk.internal.session.sync.parsing.RoomSyncAccountDataHandler +import org.matrix.android.sdk.internal.session.sync.reportSubtask import org.matrix.android.sdk.internal.util.computeBestChunkSize import org.matrix.android.sdk.internal.util.time.Clock import timber.log.Timber @@ -106,11 +107,13 @@ internal class RoomSyncHandler @Inject constructor( data class LEFT(val data: Map) : HandlingStrategy() } - 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) @@ -124,11 +127,13 @@ internal class RoomSyncHandler @Inject constructor( } // PRIVATE METHODS ***************************************************************************** - private 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 { @@ -136,24 +141,24 @@ internal class RoomSyncHandler @Inject constructor( } val syncLocalTimeStampMillis = clock.epochMillis() val rooms = when (handlingStrategy) { - is HandlingStrategy.JOINED -> { + is HandlingStrategy.JOINED -> { if (isInitialSync && initialSyncStrategy is InitialSyncStrategy.Optimized) { insertJoinRoomsFromInitSync(realm, handlingStrategy, syncLocalTimeStampMillis, aggregator, reporter) // Rooms are already inserted, return an empty list emptyList() } else { - handlingStrategy.data.mapWithProgress(reporter, InitSyncStep.ImportingAccountJoinedRooms, 0.6f) { + handlingStrategy.data.mapWithProgress(reporter, InitialSyncStep.ImportingAccountJoinedRooms, 0.6f) { handleJoinedRoom(realm, it.key, it.value, insertType, syncLocalTimeStampMillis, aggregator) } } } is HandlingStrategy.INVITED -> - handlingStrategy.data.mapWithProgress(reporter, InitSyncStep.ImportingAccountInvitedRooms, 0.1f) { + handlingStrategy.data.mapWithProgress(reporter, InitialSyncStep.ImportingAccountInvitedRooms, 0.1f) { handleInvitedRoom(realm, it.key, it.value, insertType, syncLocalTimeStampMillis) } - is HandlingStrategy.LEFT -> { - handlingStrategy.data.mapWithProgress(reporter, InitSyncStep.ImportingAccountLeftRooms, 0.3f) { + is HandlingStrategy.LEFT -> { + handlingStrategy.data.mapWithProgress(reporter, InitialSyncStep.ImportingAccountLeftRooms, 0.3f) { handleLeftRoom(realm, it.key, it.value, insertType, syncLocalTimeStampMillis) } } @@ -161,18 +166,20 @@ internal class RoomSyncHandler @Inject constructor( realm.insertOrUpdate(rooms) } - private 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 ) if (bestChunkSize.shouldChunk()) { - reportSubtask(reporter, InitSyncStep.ImportingAccountJoinedRooms, bestChunkSize.numberOfChunks, 0.6f) { + reportSubtask(reporter, InitialSyncStep.ImportingAccountJoinedRooms, bestChunkSize.numberOfChunks, 0.6f) { Timber.d("INIT_SYNC ${handlingStrategy.data.keys.size} rooms to insert, split with $bestChunkSize") // I cannot find a better way to chunk a map, so chunk the keys and then create new maps handlingStrategy.data.keys @@ -196,19 +203,21 @@ internal class RoomSyncHandler @Inject constructor( } } else { // No need to split - val rooms = handlingStrategy.data.mapWithProgress(reporter, InitSyncStep.ImportingAccountJoinedRooms, 0.6f) { + val rooms = handlingStrategy.data.mapWithProgress(reporter, InitialSyncStep.ImportingAccountJoinedRooms, 0.6f) { handleJoinedRoom(realm, it.key, it.value, EventInsertType.INITIAL_SYNC, syncLocalTimeStampMillis, aggregator) } realm.insertOrUpdate(rooms) } } - private 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 @@ -235,7 +244,7 @@ internal class RoomSyncHandler @Inject constructor( if (event.eventId == null || event.stateKey == null || event.type == null) { continue } - val ageLocalTs = event.unsignedData?.age?.let { syncLocalTimestampMillis - it } + val ageLocalTs = syncLocalTimestampMillis - (event.unsignedData?.age ?: 0) val eventEntity = event.toEntity(roomId, SendState.SYNCED, ageLocalTs).copyToRealmOrIgnore(realm, insertType) Timber.v("## received state event ${event.type} and key ${event.stateKey}") CurrentStateEventEntity.getOrCreate(realm, roomId, event.stateKey, event.type).apply { @@ -281,11 +290,13 @@ internal class RoomSyncHandler @Inject constructor( return roomEntity } - private fun handleInvitedRoom(realm: Realm, - roomId: String, - roomSync: InvitedRoomSync, - insertType: EventInsertType, - syncLocalTimestampMillis: Long): RoomEntity { + private fun handleInvitedRoom( + realm: Realm, + roomId: String, + roomSync: InvitedRoomSync, + insertType: EventInsertType, + syncLocalTimestampMillis: Long + ): RoomEntity { Timber.v("Handle invited sync for room $roomId") val isInitialSync = insertType == EventInsertType.INITIAL_SYNC val roomEntity = RoomEntity.getOrCreate(realm, roomId) @@ -295,7 +306,7 @@ internal class RoomSyncHandler @Inject constructor( if (event.stateKey == null || event.type == null) { return@forEach } - val ageLocalTs = event.unsignedData?.age?.let { syncLocalTimestampMillis - it } + val ageLocalTs = syncLocalTimestampMillis - (event.unsignedData?.age ?: 0) val eventEntity = event.toEntity(roomId, SendState.SYNCED, ageLocalTs).copyToRealmOrIgnore(realm, insertType) CurrentStateEventEntity.getOrCreate(realm, roomId, event.stateKey, event.type).apply { eventId = eventEntity.eventId @@ -312,18 +323,20 @@ internal class RoomSyncHandler @Inject constructor( return roomEntity } - private fun handleLeftRoom(realm: Realm, - roomId: String, - roomSync: RoomSync, - insertType: EventInsertType, - syncLocalTimestampMillis: Long): RoomEntity { + private fun handleLeftRoom( + realm: Realm, + roomId: String, + roomSync: RoomSync, + insertType: EventInsertType, + syncLocalTimestampMillis: Long + ): RoomEntity { val isInitialSync = insertType == EventInsertType.INITIAL_SYNC val roomEntity = RoomEntity.getOrCreate(realm, roomId) for (event in roomSync.state?.events.orEmpty()) { if (event.eventId == null || event.stateKey == null || event.type == null) { continue } - val ageLocalTs = event.unsignedData?.age?.let { syncLocalTimestampMillis - it } + val ageLocalTs = syncLocalTimestampMillis - (event.unsignedData?.age ?: 0) val eventEntity = event.toEntity(roomId, SendState.SYNCED, ageLocalTs).copyToRealmOrIgnore(realm, insertType) CurrentStateEventEntity.getOrCreate(realm, roomId, event.stateKey, event.type).apply { eventId = event.eventId @@ -335,7 +348,7 @@ internal class RoomSyncHandler @Inject constructor( if (event.eventId == null || event.senderId == null || event.type == null) { continue } - val ageLocalTs = event.unsignedData?.age?.let { syncLocalTimestampMillis - it } + val ageLocalTs = syncLocalTimestampMillis - (event.unsignedData?.age ?: 0) val eventEntity = event.toEntity(roomId, SendState.SYNCED, ageLocalTs).copyToRealmOrIgnore(realm, insertType) if (event.stateKey != null) { CurrentStateEventEntity.getOrCreate(realm, roomId, event.stateKey, event.type).apply { @@ -357,22 +370,25 @@ internal class RoomSyncHandler @Inject constructor( return roomEntity } - private 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) - } val chunkEntity = if (!isLimited && lastChunk != null) { lastChunk } else { + // Delete all chunks of the room in case of gap. + ChunkEntity.findAll(realm, roomId).forEach { + it.deleteOnCascade(deleteStateEvents = false, canDeleteRoot = true) + } realm.createObject().apply { this.prevToken = prevToken this.isLastForward = true @@ -385,7 +401,10 @@ internal class RoomSyncHandler @Inject constructor( for (rawEvent in eventList) { // It's annoying roomId is not there, but lot of code rely on it. // And had to do it now as copy would delete all decryption results.. - val event = rawEvent.copy(roomId = roomId) + val ageLocalTs = syncLocalTimestampMillis - (rawEvent.unsignedData?.age ?: 0) + val event = rawEvent.copy(roomId = roomId).also { + it.ageLocalTs = ageLocalTs + } if (event.eventId == null || event.senderId == null || event.type == null) { continue } @@ -407,7 +426,6 @@ internal class RoomSyncHandler @Inject constructor( contentToInject = threadsAwarenessHandler.makeEventThreadAware(realm, roomId, event) } - val ageLocalTs = event.unsignedData?.age?.let { syncLocalTimestampMillis - it } val eventEntity = event.toEntity(roomId, SendState.SYNCED, ageLocalTs, contentToInject).copyToRealmOrIgnore(realm, insertType) if (event.stateKey != null) { CurrentStateEventEntity.getOrCreate(realm, roomId, event.stateKey, event.type).apply { @@ -500,11 +518,13 @@ internal class RoomSyncHandler @Inject constructor( * Adds new event to the appropriate thread chunk. If the event is already in * the thread timeline and /relations api, we should not added it */ - private fun addToThreadChunkIfNeeded(realm: Realm, - roomId: String, - threadId: String, - timelineEventEntity: TimelineEventEntity?, - roomEntity: RoomEntity) { + private fun addToThreadChunkIfNeeded( + realm: Realm, + roomId: String, + threadId: String, + timelineEventEntity: TimelineEventEntity?, + roomEntity: RoomEntity + ) { val eventId = timelineEventEntity?.eventId ?: return ChunkEntity.findLastForwardChunkOfThread(realm, roomId, threadId)?.let { threadChunk -> @@ -520,9 +540,10 @@ internal class RoomSyncHandler @Inject constructor( private fun decryptIfNeeded(event: Event, roomId: String) { try { + val timelineId = generateTimelineId(roomId) // Event from sync does not have roomId, so add it to the event first // 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), "") } + val result = runBlocking { cryptoService.decryptEvent(event.copy(roomId = roomId), timelineId) } event.mxDecryptionResult = OlmDecryptionResult( payload = result.clearEvent, senderKey = result.senderCurve25519Key, @@ -537,15 +558,21 @@ internal class RoomSyncHandler @Inject constructor( } } + private fun generateTimelineId(roomId: String): String { + return "RoomSyncHandler$roomId" + } + data class EphemeralResult( val typingUserIds: List = emptyList() ) - private fun handleEphemeral(realm: Realm, - roomId: String, - ephemeralEvents: List, - isInitialSync: Boolean, - aggregator: SyncResponsePostTreatmentAggregator): EphemeralResult { + private fun handleEphemeral( + realm: Realm, + roomId: String, + ephemeralEvents: List, + isInitialSync: Boolean, + aggregator: SyncResponsePostTreatmentAggregator + ): EphemeralResult { var result = EphemeralResult() for (event in ephemeralEvents) { when (event.type) { @@ -555,12 +582,12 @@ internal class RoomSyncHandler @Inject constructor( readReceiptHandler.handle(realm, roomId, readReceiptContent, isInitialSync, aggregator) } } - EventType.TYPING -> { + EventType.TYPING -> { event.content.toModel()?.let { typingEventContent -> result = result.copy(typingUserIds = typingEventContent.typingUserIds) } } - else -> Timber.w("Ephemeral event type '${event.type}' not yet supported") + else -> Timber.w("Ephemeral event type '${event.type}' not yet supported") } } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/handler/room/RoomTypingUsersHandler.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/handler/room/RoomTypingUsersHandler.kt index 63db13a5b8..54bb63753c 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/handler/room/RoomTypingUsersHandler.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/handler/room/RoomTypingUsersHandler.kt @@ -23,8 +23,10 @@ import org.matrix.android.sdk.internal.session.room.membership.RoomMemberHelper import org.matrix.android.sdk.internal.session.typing.DefaultTypingUsersTracker import javax.inject.Inject -internal class RoomTypingUsersHandler @Inject constructor(@UserId private val userId: String, - private val typingUsersTracker: DefaultTypingUsersTracker) { +internal class RoomTypingUsersHandler @Inject constructor( + @UserId private val userId: String, + private val typingUsersTracker: DefaultTypingUsersTracker +) { // TODO This could be handled outside of the Realm transaction. Use the new aggregator? fun handle(realm: Realm, roomId: String, ephemeralResult: RoomSyncHandler.EphemeralResult?) { diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/handler/room/ThreadsAwarenessHandler.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/handler/room/ThreadsAwarenessHandler.kt index 9beb8333a4..70553359ff 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/handler/room/ThreadsAwarenessHandler.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/handler/room/ThreadsAwarenessHandler.kt @@ -53,6 +53,7 @@ import org.matrix.android.sdk.internal.session.permalinks.PermalinkFactory import org.matrix.android.sdk.internal.session.room.send.LocalEchoEventFactory import org.matrix.android.sdk.internal.session.room.timeline.GetEventTask import org.matrix.android.sdk.internal.util.awaitTransaction +import org.matrix.android.sdk.internal.util.time.Clock import javax.inject.Inject /** @@ -64,7 +65,8 @@ internal class ThreadsAwarenessHandler @Inject constructor( private val permalinkFactory: PermalinkFactory, @SessionDatabase private val monarchy: Monarchy, private val lightweightSettingsStorage: LightweightSettingsStorage, - private val getEventTask: GetEventTask + private val getEventTask: GetEventTask, + private val clock: Clock, ) { // This caching is responsible to improve the performance when we receive a root event @@ -120,7 +122,7 @@ internal class ThreadsAwarenessHandler @Inject constructor( private suspend fun fetchThreadsEvents(threadsToFetch: Map) { val eventEntityList = threadsToFetch.mapNotNull { (eventId, roomId) -> fetchEvent(eventId, roomId)?.let { - it.toEntity(roomId, SendState.SYNCED, it.ageLocalTs) + it.toEntity(roomId, SendState.SYNCED, it.ageLocalTs ?: clock.epochMillis()) } } @@ -156,10 +158,12 @@ internal class ThreadsAwarenessHandler @Inject constructor( * Handle events mainly coming from the RoomSyncHandler. * @return The content to inject in the roomSyncHandler live events */ - fun makeEventThreadAware(realm: Realm, - roomId: String?, - event: Event?, - eventEntity: EventEntity? = null): String? { + fun makeEventThreadAware( + realm: Realm, + roomId: String?, + event: Event?, + eventEntity: EventEntity? = null + ): String? { event ?: return null roomId ?: return null if (lightweightSettingsStorage.areThreadMessagesEnabled() && !isReplyEvent(event)) return null @@ -271,10 +275,12 @@ internal class ThreadsAwarenessHandler @Inject constructor( * Actual update the eventEntity with the new payload. * @return the content to inject when this is executed by RoomSyncHandler */ - private fun updateEventEntity(event: Event, - eventEntity: EventEntity?, - eventPayload: MutableMap, - messageTextContent: Content): String? { + private fun updateEventEntity( + event: Event, + eventEntity: EventEntity?, + eventPayload: MutableMap, + messageTextContent: Content + ): String? { eventPayload["content"] = messageTextContent if (event.isEncrypted()) { @@ -304,11 +310,12 @@ internal class ThreadsAwarenessHandler @Inject constructor( * @param threadRelation the information about thread * @return The final content with the injected event */ - private fun injectEvent(roomId: String, - eventBody: String, - eventToInject: Event, - eventToInjectBody: String, - threadRelation: RelationDefaultContent? + private fun injectEvent( + roomId: String, + eventBody: String, + eventToInject: Event, + eventToInjectBody: String, + threadRelation: RelationDefaultContent? ): Content? { val eventToInjectId = eventToInject.eventId ?: return null val eventIdToInjectSenderId = eventToInject.senderId.orEmpty() @@ -334,11 +341,13 @@ internal class ThreadsAwarenessHandler @Inject constructor( /** * Integrate fallback Quote reply. */ - private fun injectFallbackIndicator(event: Event, - eventBody: String, - eventEntity: EventEntity?, - eventPayload: MutableMap, - threadRelation: RelationDefaultContent?): String? { + private fun injectFallbackIndicator( + event: Event, + eventBody: String, + eventEntity: EventEntity?, + eventPayload: MutableMap, + threadRelation: RelationDefaultContent? + ): String? { val replyFormatted = LocalEchoEventFactory.QUOTE_PATTERN.format( "In reply to a thread", eventBody diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/job/SyncThread.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/job/SyncThread.kt index fc6a4e03d6..24a60a80da 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/job/SyncThread.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/job/SyncThread.kt @@ -56,11 +56,12 @@ private const val DEFAULT_LONG_POOL_TIMEOUT = 30_000L private val loggerTag = LoggerTag("SyncThread", LoggerTag.SYNC) -internal class SyncThread @Inject constructor(private val syncTask: SyncTask, - private val networkConnectivityChecker: NetworkConnectivityChecker, - private val backgroundDetectionObserver: BackgroundDetectionObserver, - private val activeCallHandler: ActiveCallHandler, - private val lightweightSettingsStorage: DefaultLightweightSettingsStorage +internal class SyncThread @Inject constructor( + private val syncTask: SyncTask, + private val networkConnectivityChecker: NetworkConnectivityChecker, + private val backgroundDetectionObserver: BackgroundDetectionObserver, + private val activeCallHandler: ActiveCallHandler, + private val lightweightSettingsStorage: DefaultLightweightSettingsStorage ) : Thread("SyncThread"), NetworkConnectivityChecker.Listener, BackgroundDetectionObserver.Listener { private var state: SyncState = SyncState.Idle @@ -179,8 +180,8 @@ internal class SyncThread @Inject constructor(private val syncTask: SyncTask, val afterPause = state.let { it is SyncState.Running && it.afterPause } val timeout = when { previousSyncResponseHasToDevice -> 0L /* Force timeout to 0 */ - afterPause -> 0L /* No timeout after a pause */ - else -> DEFAULT_LONG_POOL_TIMEOUT + afterPause -> 0L /* No timeout after a pause */ + else -> DEFAULT_LONG_POOL_TIMEOUT } Timber.tag(loggerTag.value).d("Execute sync request with timeout $timeout") val presence = lightweightSettingsStorage.getSyncPresenceStatus() 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 bbb18b664c..0cc7944d58 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 @@ -119,9 +119,11 @@ internal class SyncWorker(context: Context, workerParameters: WorkerParameters, companion object { private const val BG_SYNC_WORK_NAME = "BG_SYNCP" - fun requireBackgroundSync(workManagerProvider: WorkManagerProvider, - sessionId: String, - serverTimeoutInSeconds: Long = 0) { + fun requireBackgroundSync( + workManagerProvider: WorkManagerProvider, + sessionId: String, + serverTimeoutInSeconds: Long = 0 + ) { val data = WorkerParamsFactory.toData( Params( sessionId = sessionId, @@ -139,11 +141,13 @@ internal class SyncWorker(context: Context, workerParameters: WorkerParameters, .enqueueUniqueWork(BG_SYNC_WORK_NAME, ExistingWorkPolicy.APPEND_OR_REPLACE, workRequest) } - fun automaticallyBackgroundSync(workManagerProvider: WorkManagerProvider, - sessionId: String, - serverTimeoutInSeconds: Long = 0, - delayInSeconds: Long = 30, - forceImmediate: Boolean = false) { + fun automaticallyBackgroundSync( + workManagerProvider: WorkManagerProvider, + sessionId: String, + serverTimeoutInSeconds: Long = 0, + delayInSeconds: Long = 30, + forceImmediate: Boolean = false + ) { val data = WorkerParamsFactory.toData( Params( sessionId = sessionId, diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/model/TokensChunkResponse.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/model/TokensChunkResponse.kt index 533ba70b05..40fdcbbb84 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/model/TokensChunkResponse.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/model/TokensChunkResponse.kt @@ -22,4 +22,5 @@ import com.squareup.moshi.JsonClass internal data class TokensChunkResponse( val start: String? = null, val end: String? = null, - val chunk: List? = null) + val chunk: List? = null +) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/parsing/RoomSyncAccountDataHandler.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/parsing/RoomSyncAccountDataHandler.kt index 5e7bde87e7..b1b2bfef33 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/parsing/RoomSyncAccountDataHandler.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/parsing/RoomSyncAccountDataHandler.kt @@ -32,8 +32,10 @@ import org.matrix.android.sdk.internal.session.sync.handler.room.RoomFullyReadHa import org.matrix.android.sdk.internal.session.sync.handler.room.RoomTagHandler import javax.inject.Inject -internal class RoomSyncAccountDataHandler @Inject constructor(private val roomTagHandler: RoomTagHandler, - private val roomFullyReadHandler: RoomFullyReadHandler) { +internal class RoomSyncAccountDataHandler @Inject constructor( + private val roomTagHandler: RoomTagHandler, + private val roomFullyReadHandler: RoomFullyReadHandler +) { fun handle(realm: Realm, roomId: String, accountData: RoomSyncAccountData) { if (accountData.events.isNullOrEmpty()) { diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/terms/DefaultTermsService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/terms/DefaultTermsService.kt index 9876643bed..b687f5833a 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/terms/DefaultTermsService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/terms/DefaultTermsService.kt @@ -51,8 +51,10 @@ internal class DefaultTermsService @Inject constructor( private val updateUserAccountDataTask: UpdateUserAccountDataTask ) : TermsService { - override suspend fun getTerms(serviceType: TermsService.ServiceType, - baseUrl: String): GetTermsResponse { + override suspend fun getTerms( + serviceType: TermsService.ServiceType, + baseUrl: String + ): GetTermsResponse { val url = buildUrl(baseUrl, serviceType) val termsResponse = executeRequest(null) { termsAPI.getTerms("${url}terms") @@ -90,10 +92,12 @@ internal class DefaultTermsService @Inject constructor( } } - override suspend fun agreeToTerms(serviceType: TermsService.ServiceType, - baseUrl: String, - agreedUrls: List, - token: String?) { + override suspend fun agreeToTerms( + serviceType: TermsService.ServiceType, + baseUrl: String, + agreedUrls: List, + token: String? + ) { val url = buildUrl(baseUrl, serviceType) val tokenToUse = token?.takeIf { it.isNotEmpty() } ?: getToken(baseUrl) @@ -128,7 +132,7 @@ internal class DefaultTermsService @Inject constructor( private fun buildUrl(baseUrl: String, serviceType: TermsService.ServiceType): String { val servicePath = when (serviceType) { TermsService.ServiceType.IntegrationManager -> NetworkConstants.URI_INTEGRATION_MANAGER_PATH - TermsService.ServiceType.IdentityService -> NetworkConstants.URI_IDENTITY_PATH_V2 + TermsService.ServiceType.IdentityService -> NetworkConstants.URI_IDENTITY_PATH_V2 } return "${baseUrl.ensureTrailingSlash()}$servicePath" } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/terms/TermsAPI.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/terms/TermsAPI.kt index f6f57bf0ef..6a3328ee52 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/terms/TermsAPI.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/terms/TermsAPI.kt @@ -37,15 +37,19 @@ internal interface TermsAPI { * This request requires authentication. */ @POST - suspend fun agreeToTerms(@Url url: String, - @Body params: AcceptTermsBody, - @Header(HttpHeaders.Authorization) token: String) + suspend fun agreeToTerms( + @Url url: String, + @Body params: AcceptTermsBody, + @Header(HttpHeaders.Authorization) token: String + ) /** * API to retrieve the terms for a homeserver. The API /terms does not exist yet, so retrieve the terms from the login flow. * We do not care about the result (Credentials). */ @POST - suspend fun register(@Url url: String, - @Body body: JsonDict = emptyJsonDict) + suspend fun register( + @Url url: String, + @Body body: JsonDict = emptyJsonDict + ) } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/terms/TermsModule.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/terms/TermsModule.kt index d7b6f68add..06d12bfe76 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/terms/TermsModule.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/terms/TermsModule.kt @@ -34,8 +34,10 @@ internal abstract class TermsModule { @Provides @JvmStatic @SessionScope - fun providesTermsAPI(@UnauthenticatedWithCertificate unauthenticatedOkHttpClient: Lazy, - retrofitFactory: RetrofitFactory): TermsAPI { + fun providesTermsAPI( + @UnauthenticatedWithCertificate unauthenticatedOkHttpClient: Lazy, + retrofitFactory: RetrofitFactory + ): TermsAPI { val retrofit = retrofitFactory.create(unauthenticatedOkHttpClient, "https://foo.bar") return retrofit.create(TermsAPI::class.java) } 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 210cb192e7..c8b9eaf810 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 @@ -21,8 +21,10 @@ import org.matrix.android.sdk.api.session.thirdparty.ThirdPartyService import org.matrix.android.sdk.api.session.thirdparty.model.ThirdPartyUser import javax.inject.Inject -internal class DefaultThirdPartyService @Inject constructor(private val getThirdPartyProtocolTask: GetThirdPartyProtocolsTask, - private val getThirdPartyUserTask: GetThirdPartyUserTask) : +internal class DefaultThirdPartyService @Inject constructor( + private val getThirdPartyProtocolTask: GetThirdPartyProtocolsTask, + private val getThirdPartyUserTask: GetThirdPartyUserTask +) : ThirdPartyService { override suspend fun getThirdPartyProtocols(): Map { diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/thirdparty/ThirdPartyAPI.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/thirdparty/ThirdPartyAPI.kt index 3ecc39ac94..6759205bf3 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/thirdparty/ThirdPartyAPI.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/thirdparty/ThirdPartyAPI.kt @@ -39,6 +39,8 @@ internal interface ThirdPartyAPI { * Ref: https://matrix.org/docs/spec/client_server/r0.6.1#get-matrix-client-r0-thirdparty-user-protocol */ @GET(NetworkConstants.URI_API_PREFIX_PATH_R0 + "thirdparty/user/{protocol}") - suspend fun getThirdPartyUser(@Path("protocol") protocol: String, - @QueryMap params: Map?): List + suspend fun getThirdPartyUser( + @Path("protocol") protocol: String, + @QueryMap params: Map? + ): List } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/user/DefaultUserService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/user/DefaultUserService.kt index 4ffc42e714..660cf118df 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/user/DefaultUserService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/user/DefaultUserService.kt @@ -26,10 +26,12 @@ import org.matrix.android.sdk.internal.session.user.accountdata.UpdateIgnoredUse import org.matrix.android.sdk.internal.session.user.model.SearchUserTask import javax.inject.Inject -internal class DefaultUserService @Inject constructor(private val userDataSource: UserDataSource, - private val searchUserTask: SearchUserTask, - private val updateIgnoredUserIdsTask: UpdateIgnoredUserIdsTask, - private val getProfileInfoTask: GetProfileInfoTask) : UserService { +internal class DefaultUserService @Inject constructor( + private val userDataSource: UserDataSource, + private val searchUserTask: SearchUserTask, + private val updateIgnoredUserIdsTask: UpdateIgnoredUserIdsTask, + private val getProfileInfoTask: GetProfileInfoTask +) : UserService { override fun getUser(userId: String): User? { return userDataSource.getUser(userId) @@ -59,9 +61,11 @@ internal class DefaultUserService @Inject constructor(private val userDataSource return userDataSource.getIgnoredUsersLive() } - override suspend fun searchUsersDirectory(search: String, - limit: Int, - excludedUserIds: Set): List { + override suspend fun searchUsersDirectory( + search: String, + limit: Int, + excludedUserIds: Set + ): List { val params = SearchUserTask.Params(limit, search, excludedUserIds) return searchUserTask.execute(params) } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/user/UserDataSource.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/user/UserDataSource.kt index e83725b10d..f9feb04e97 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/user/UserDataSource.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/user/UserDataSource.kt @@ -36,8 +36,10 @@ import org.matrix.android.sdk.internal.database.query.where import org.matrix.android.sdk.internal.di.SessionDatabase import javax.inject.Inject -internal class UserDataSource @Inject constructor(@SessionDatabase private val monarchy: Monarchy, - private val realmSessionProvider: RealmSessionProvider) { +internal class UserDataSource @Inject constructor( + @SessionDatabase private val monarchy: Monarchy, + private val realmSessionProvider: RealmSessionProvider +) { private val realmDataSourceFactory: Monarchy.RealmDataSourceFactory by lazy { monarchy.createDataSourceFactory { realm -> diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/user/accountdata/AccountDataAPI.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/user/accountdata/AccountDataAPI.kt index 178f349ec8..b283d51845 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/user/accountdata/AccountDataAPI.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/user/accountdata/AccountDataAPI.kt @@ -31,7 +31,9 @@ internal interface AccountDataAPI { * @param params the put params */ @PUT(NetworkConstants.URI_API_PREFIX_PATH_R0 + "user/{userId}/account_data/{type}") - suspend fun setAccountData(@Path("userId") userId: String, - @Path("type") type: String, - @Body params: Any) + suspend fun setAccountData( + @Path("userId") userId: String, + @Path("type") type: String, + @Body params: Any + ) } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/user/accountdata/UpdateUserAccountDataTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/user/accountdata/UpdateUserAccountDataTask.kt index 88db381852..ff0e960f0f 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/user/accountdata/UpdateUserAccountDataTask.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/user/accountdata/UpdateUserAccountDataTask.kt @@ -35,8 +35,9 @@ internal interface UpdateUserAccountDataTask : Task> + data class DirectChatParams( + override val type: String = UserAccountDataTypes.TYPE_DIRECT_MESSAGES, + private val directMessages: Map> ) : Params { override fun getData(): Any { @@ -63,8 +66,9 @@ internal interface UpdateUserAccountDataTask : Task { ) } -internal class DefaultCreateWidgetTask @Inject constructor(@SessionDatabase private val monarchy: Monarchy, - private val roomAPI: RoomAPI, - @UserId private val userId: String, - private val globalErrorReceiver: GlobalErrorReceiver) : CreateWidgetTask { +internal class DefaultCreateWidgetTask @Inject constructor( + @SessionDatabase private val monarchy: Monarchy, + private val roomAPI: RoomAPI, + @UserId private val userId: String, + private val globalErrorReceiver: GlobalErrorReceiver +) : CreateWidgetTask { override suspend fun execute(params: CreateWidgetTask.Params): String { val response = executeRequest(globalErrorReceiver) { 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 857105f6ef..2665809d9a 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 @@ -27,8 +27,10 @@ import timber.log.Timber import java.lang.reflect.Type import javax.inject.Inject -internal class DefaultWidgetPostAPIMediator @Inject constructor(private val moshi: Moshi, - private val widgetPostMessageAPIProvider: WidgetPostMessageAPIProvider) : +internal class DefaultWidgetPostAPIMediator @Inject constructor( + private val moshi: Moshi, + private val widgetPostMessageAPIProvider: WidgetPostMessageAPIProvider +) : 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 53a435d217..9c59c11345 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 @@ -17,7 +17,7 @@ package org.matrix.android.sdk.internal.session.widgets import androidx.lifecycle.LiveData -import org.matrix.android.sdk.api.query.QueryStringValue +import org.matrix.android.sdk.api.query.QueryStateEventValue import org.matrix.android.sdk.api.session.events.model.Content import org.matrix.android.sdk.api.session.widgets.WidgetPostAPIMediator import org.matrix.android.sdk.api.session.widgets.WidgetService @@ -26,9 +26,11 @@ import org.matrix.android.sdk.api.session.widgets.model.Widget import javax.inject.Inject import javax.inject.Provider -internal class DefaultWidgetService @Inject constructor(private val widgetManager: WidgetManager, - private val widgetURLFormatter: WidgetURLFormatter, - private val widgetPostAPIMediator: Provider) : +internal class DefaultWidgetService @Inject constructor( + private val widgetManager: WidgetManager, + private val widgetURLFormatter: WidgetURLFormatter, + private val widgetPostAPIMediator: Provider +) : WidgetService { override fun getWidgetURLFormatter(): WidgetURLFormatter { @@ -41,7 +43,7 @@ internal class DefaultWidgetService @Inject constructor(private val widgetManage override fun getRoomWidgets( roomId: String, - widgetId: QueryStringValue, + widgetId: QueryStateEventValue, widgetTypes: Set?, excludedTypes: Set? ): List { @@ -54,7 +56,7 @@ internal class DefaultWidgetService @Inject constructor(private val widgetManage override fun getRoomWidgetsLive( roomId: String, - widgetId: QueryStringValue, + widgetId: QueryStateEventValue, widgetTypes: Set?, excludedTypes: Set? ): LiveData> { diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/widgets/DefaultWidgetURLFormatter.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/widgets/DefaultWidgetURLFormatter.kt index 5879b62446..019edf21b6 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/widgets/DefaultWidgetURLFormatter.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/widgets/DefaultWidgetURLFormatter.kt @@ -30,9 +30,10 @@ import org.matrix.android.sdk.internal.session.widgets.token.GetScalarTokenTask import javax.inject.Inject @SessionScope -internal class DefaultWidgetURLFormatter @Inject constructor(private val integrationManager: IntegrationManager, - private val getScalarTokenTask: GetScalarTokenTask, - private val matrixConfiguration: MatrixConfiguration +internal class DefaultWidgetURLFormatter @Inject constructor( + private val integrationManager: IntegrationManager, + private val getScalarTokenTask: GetScalarTokenTask, + private val matrixConfiguration: MatrixConfiguration ) : IntegrationManagerService.Listener, WidgetURLFormatter, SessionLifecycleObserver { private lateinit var currentConfig: IntegrationManagerConfig 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 07ed91c179..37a329af34 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 @@ -21,6 +21,7 @@ import androidx.lifecycle.LifecycleOwner import androidx.lifecycle.LifecycleRegistry import androidx.lifecycle.LiveData import androidx.lifecycle.Transformations +import org.matrix.android.sdk.api.query.QueryStateEventValue import org.matrix.android.sdk.api.query.QueryStringValue import org.matrix.android.sdk.api.session.Session import org.matrix.android.sdk.api.session.SessionLifecycleObserver @@ -45,12 +46,14 @@ import org.matrix.android.sdk.internal.session.widgets.helper.extractWidgetSeque import javax.inject.Inject @SessionScope -internal class WidgetManager @Inject constructor(private val integrationManager: IntegrationManager, - private val userAccountDataDataSource: UserAccountDataDataSource, - private val stateEventDataSource: StateEventDataSource, - private val createWidgetTask: CreateWidgetTask, - private val widgetFactory: WidgetFactory, - @UserId private val userId: String) : +internal class WidgetManager @Inject constructor( + private val integrationManager: IntegrationManager, + private val userAccountDataDataSource: UserAccountDataDataSource, + private val stateEventDataSource: StateEventDataSource, + private val createWidgetTask: CreateWidgetTask, + private val widgetFactory: WidgetFactory, + @UserId private val userId: String +) : IntegrationManagerService.Listener, SessionLifecycleObserver { @@ -69,7 +72,7 @@ internal class WidgetManager @Inject constructor(private val integrationManager: fun getRoomWidgetsLive( roomId: String, - widgetId: QueryStringValue = QueryStringValue.NoCondition, + widgetId: QueryStateEventValue, widgetTypes: Set? = null, excludedTypes: Set? = null ): LiveData> { @@ -86,7 +89,7 @@ internal class WidgetManager @Inject constructor(private val integrationManager: fun getRoomWidgets( roomId: String, - widgetId: QueryStringValue = QueryStringValue.NoCondition, + widgetId: QueryStateEventValue, widgetTypes: Set? = null, excludedTypes: Set? = null ): List { @@ -103,8 +106,10 @@ internal class WidgetManager @Inject constructor(private val integrationManager: return widgetFactory.computeURL(widget, isLightTheme) } - private fun List.mapEventsToWidgets(widgetTypes: Set? = null, - excludedTypes: Set? = null): List { + private fun List.mapEventsToWidgets( + widgetTypes: Set? = null, + excludedTypes: Set? = null + ): List { val widgetEvents = this // Widget id -> widget val widgets: MutableMap = HashMap() @@ -149,8 +154,10 @@ internal class WidgetManager @Inject constructor(private val integrationManager: return widgetsAccountData.mapToWidgets(widgetTypes, excludedTypes) } - private fun UserAccountDataEvent.mapToWidgets(widgetTypes: Set? = null, - excludedTypes: Set? = null): List { + private fun UserAccountDataEvent.mapToWidgets( + widgetTypes: Set? = null, + excludedTypes: Set? = null + ): List { return extractWidgetSequence(widgetFactory) .filter { val widgetType = it.widgetContent.type ?: return@filter false @@ -193,7 +200,7 @@ internal class WidgetManager @Inject constructor(private val integrationManager: val powerLevelsEvent = stateEventDataSource.getStateEvent( roomId = roomId, eventType = EventType.STATE_ROOM_POWER_LEVELS, - stateKey = QueryStringValue.NoCondition + stateKey = QueryStringValue.IsEmpty ) val powerLevelsContent = powerLevelsEvent?.content?.toModel() ?: return false return PowerLevelsHelper(powerLevelsContent).isUserAllowedToSend(userId, true, EventType.STATE_ROOM_WIDGET_LEGACY) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/widgets/WidgetsAPI.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/widgets/WidgetsAPI.kt index 97b40e545e..2a4af9ffaa 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/widgets/WidgetsAPI.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/widgets/WidgetsAPI.kt @@ -30,10 +30,14 @@ internal interface WidgetsAPI { * @param version the widget API version */ @POST("register") - suspend fun register(@Body body: OpenIdToken, - @Query("v") version: String?): RegisterWidgetResponse + suspend fun register( + @Body body: OpenIdToken, + @Query("v") version: String? + ): RegisterWidgetResponse @GET("account") - suspend fun validateToken(@Query("scalar_token") scalarToken: String?, - @Query("v") version: String?) + suspend fun validateToken( + @Query("scalar_token") scalarToken: String?, + @Query("v") version: String? + ) } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/widgets/WidgetsAPIProvider.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/widgets/WidgetsAPIProvider.kt index 48c8fcdb03..25caadbade 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/widgets/WidgetsAPIProvider.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/widgets/WidgetsAPIProvider.kt @@ -24,8 +24,10 @@ import org.matrix.android.sdk.internal.session.SessionScope import javax.inject.Inject @SessionScope -internal class WidgetsAPIProvider @Inject constructor(@Unauthenticated private val okHttpClient: Lazy, - private val retrofitFactory: RetrofitFactory) { +internal class WidgetsAPIProvider @Inject constructor( + @Unauthenticated private val okHttpClient: Lazy, + private val retrofitFactory: RetrofitFactory +) { // Map to keep one WidgetAPI instance by serverUrl private val widgetsAPIs = mutableMapOf() diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/widgets/helper/WidgetFactory.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/widgets/helper/WidgetFactory.kt index a5e74a8af0..8bd61a7bdf 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/widgets/helper/WidgetFactory.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/widgets/helper/WidgetFactory.kt @@ -33,11 +33,13 @@ import org.matrix.android.sdk.internal.session.user.UserDataSource import java.net.URLEncoder import javax.inject.Inject -internal class WidgetFactory @Inject constructor(private val userDataSource: UserDataSource, - private val realmSessionProvider: RealmSessionProvider, - private val displayNameResolver: DisplayNameResolver, - private val urlResolver: ContentUrlResolver, - @UserId private val userId: String) { +internal class WidgetFactory @Inject constructor( + private val userDataSource: UserDataSource, + private val realmSessionProvider: RealmSessionProvider, + private val displayNameResolver: DisplayNameResolver, + private val urlResolver: ContentUrlResolver, + @UserId private val userId: String +) { fun create(widgetEvent: Event): Widget? { val widgetContent = widgetEvent.content.toModel() diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/widgets/token/GetScalarTokenTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/widgets/token/GetScalarTokenTask.kt index 17797cad52..49ae52afaa 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/widgets/token/GetScalarTokenTask.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/widgets/token/GetScalarTokenTask.kt @@ -37,9 +37,11 @@ internal interface GetScalarTokenTask : Task private const val WIDGET_API_VERSION = "1.1" -internal class DefaultGetScalarTokenTask @Inject constructor(private val widgetsAPIProvider: WidgetsAPIProvider, - private val scalarTokenStore: ScalarTokenStore, - private val getOpenIdTokenTask: GetOpenIdTokenTask) : GetScalarTokenTask { +internal class DefaultGetScalarTokenTask @Inject constructor( + private val widgetsAPIProvider: WidgetsAPIProvider, + private val scalarTokenStore: ScalarTokenStore, + private val getOpenIdTokenTask: GetOpenIdTokenTask +) : GetScalarTokenTask { override suspend fun execute(params: GetScalarTokenTask.Params): String { val widgetsAPI = widgetsAPIProvider.get(params.serverUrl) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/task/ConfigurableTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/task/ConfigurableTask.kt index bc80cf7ee8..eead25b57c 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/task/ConfigurableTask.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/task/ConfigurableTask.kt @@ -21,8 +21,9 @@ import org.matrix.android.sdk.api.NoOpMatrixCallback import org.matrix.android.sdk.api.util.Cancelable import java.util.UUID -internal fun Task.configureWith(params: PARAMS, - init: (ConfigurableTask.Builder.() -> Unit) = {} +internal fun Task.configureWith( + params: PARAMS, + init: (ConfigurableTask.Builder.() -> Unit) = {} ): ConfigurableTask { return ConfigurableTask.Builder(this, params).apply(init).build() } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/task/TaskExecutor.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/task/TaskExecutor.kt index 57574a96fa..5e685bd546 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/task/TaskExecutor.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/task/TaskExecutor.kt @@ -57,11 +57,11 @@ internal class TaskExecutor @Inject constructor(private val coroutineDispatchers fun cancelAll() = executorScope.coroutineContext.cancelChildren() private fun TaskThread.toDispatcher() = when (this) { - TaskThread.MAIN -> coroutineDispatchers.main + TaskThread.MAIN -> coroutineDispatchers.main TaskThread.COMPUTATION -> coroutineDispatchers.computation - TaskThread.IO -> coroutineDispatchers.io - TaskThread.CALLER -> EmptyCoroutineContext - TaskThread.CRYPTO -> coroutineDispatchers.crypto - TaskThread.DM_VERIF -> coroutineDispatchers.dmVerif + TaskThread.IO -> coroutineDispatchers.io + TaskThread.CALLER -> EmptyCoroutineContext + TaskThread.CRYPTO -> coroutineDispatchers.crypto + TaskThread.DM_VERIF -> coroutineDispatchers.dmVerif } } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/util/CancelableWork.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/util/CancelableWork.kt index 6b04cea936..2240a408ef 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/util/CancelableWork.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/util/CancelableWork.kt @@ -20,8 +20,10 @@ import androidx.work.WorkManager import org.matrix.android.sdk.api.util.Cancelable import java.util.UUID -internal class CancelableWork(private val workManager: WorkManager, - private val workId: UUID) : Cancelable { +internal class CancelableWork( + private val workManager: WorkManager, + private val workId: UUID +) : Cancelable { override fun cancel() { workManager.cancelWorkById(workId) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/util/Glob.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/util/Glob.kt index 60f9609586..a4aae80348 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/util/Glob.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/util/Glob.kt @@ -27,9 +27,9 @@ internal fun String.simpleGlobToRegExp(): String { // append("^") string.forEach { char -> when (char) { - '*' -> append(".*") - '?' -> append(".") - '.' -> append("\\.") + '*' -> append(".*") + '?' -> append(".") + '.' -> append("\\.") '\\' -> append("\\\\") else -> append(char) } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/util/JsonCanonicalizer.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/util/JsonCanonicalizer.kt index c50b7fe675..6e6007f491 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/util/JsonCanonicalizer.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/util/JsonCanonicalizer.kt @@ -58,7 +58,7 @@ internal object JsonCanonicalizer { */ private fun canonicalizeRecursive(any: Any): String { when (any) { - is JSONArray -> { + is JSONArray -> { // Canonicalize each element of the array return (0 until any.length()).joinToString(separator = ",", prefix = "[", postfix = "]") { canonicalizeRecursive(any.get(it)) @@ -88,8 +88,8 @@ internal object JsonCanonicalizer { append("}") } } - is String -> return JSONObject.quote(any) - else -> return any.toString() + is String -> return JSONObject.quote(any) + else -> return any.toString() } } } 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 ffad0b856c..2306ddf850 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 @@ -33,10 +33,12 @@ internal fun Collection.logLimit(maxQuantity: Int = 5): String { } } -internal suspend fun logDuration(message: String, - loggerTag: LoggerTag, - clock: Clock, - block: suspend () -> T): T { +internal suspend fun logDuration( + message: String, + loggerTag: LoggerTag, + clock: Clock, + block: suspend () -> T +): T { Timber.tag(loggerTag.value).d("$message -- BEGIN") val start = clock.epochMillis() val result = logRamUsage(message) { diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/util/UrlUtils.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/util/UrlUtils.kt index fbbec1a100..cbaec586d0 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/util/UrlUtils.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/util/UrlUtils.kt @@ -32,9 +32,9 @@ internal fun String.isValidUrl(): Boolean { */ internal fun String.ensureProtocol(): String { return when { - isEmpty() -> this + isEmpty() -> this !startsWith("http") -> "https://$this" - else -> this + else -> this } } @@ -43,8 +43,8 @@ internal fun String.ensureProtocol(): String { */ internal fun String.ensureTrailingSlash(): String { return when { - isEmpty() -> this + isEmpty() -> this !endsWith("/") -> "$this/" - else -> this + else -> this } } 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 8f3c89f2d4..8da1bed97e 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 @@ -20,8 +20,10 @@ import io.realm.DynamicRealm import io.realm.RealmObjectSchema import timber.log.Timber -internal abstract class RealmMigrator(private val realm: DynamicRealm, - private val targetSchemaVersion: Int) { +internal abstract class RealmMigrator( + private val realm: DynamicRealm, + 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/SystemModule.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/util/system/SystemModule.kt index 396d12f369..8c7d7704ed 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,6 +18,8 @@ package org.matrix.android.sdk.internal.util.system import dagger.Binds import dagger.Module +import org.matrix.android.sdk.api.securestorage.SecureStorageService +import org.matrix.android.sdk.internal.securestorage.DefaultSecureStorageService import org.matrix.android.sdk.internal.util.time.Clock import org.matrix.android.sdk.internal.util.time.DefaultClock @@ -25,7 +27,7 @@ import org.matrix.android.sdk.internal.util.time.DefaultClock internal abstract class SystemModule { @Binds - abstract fun bindBuildVersionSdkIntProvider(provider: DefaultBuildVersionSdkIntProvider): BuildVersionSdkIntProvider + abstract fun bindSecureStorageService(service: DefaultSecureStorageService): SecureStorageService @Binds abstract fun bindClock(clock: DefaultClock): Clock diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/wellknown/GetWellknownTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/wellknown/GetWellknownTask.kt index 31549155d3..003bdab9cb 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/wellknown/GetWellknownTask.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/wellknown/GetWellknownTask.kt @@ -101,25 +101,25 @@ internal class DefaultGetWellknownTask @Inject constructor( } } catch (throwable: Throwable) { when (throwable) { - is UnrecognizedCertificateException -> { + is UnrecognizedCertificateException -> { throw Failure.UnrecognizedCertificateFailure( "https://$domain", throwable.fingerprint ) } - is Failure.NetworkConnection -> { + is Failure.NetworkConnection -> { WellknownResult.Ignore } - is Failure.OtherServerError -> { + is Failure.OtherServerError -> { when (throwable.httpCode) { HttpsURLConnection.HTTP_NOT_FOUND -> WellknownResult.Ignore - else -> WellknownResult.FailPrompt(null, null) + else -> WellknownResult.FailPrompt(null, null) } } is MalformedJsonException, is EOFException -> { WellknownResult.FailPrompt(null, null) } - else -> { + else -> { throw throwable } } 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 95b3662c67..52146ef484 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 @@ -27,6 +27,7 @@ import org.matrix.android.sdk.internal.di.MatrixScope import org.matrix.android.sdk.internal.session.content.UploadContentWorker import org.matrix.android.sdk.internal.session.group.GetGroupDataWorker import org.matrix.android.sdk.internal.session.pushers.AddPusherWorker +import org.matrix.android.sdk.internal.session.room.aggregation.livelocation.DeactivateLiveLocationShareWorker import org.matrix.android.sdk.internal.session.room.send.MultipleEventSendingDispatcherWorker import org.matrix.android.sdk.internal.session.room.send.RedactEventWorker import org.matrix.android.sdk.internal.session.room.send.SendEventWorker @@ -48,25 +49,27 @@ internal class MatrixWorkerFactory @Inject constructor(private val sessionManage ): ListenableWorker? { Timber.d("MatrixWorkerFactory.createWorker for $workerClassName") return when (workerClassName) { - CheckFactoryWorker::class.java.name -> + CheckFactoryWorker::class.java.name -> CheckFactoryWorker(appContext, workerParameters, true) - AddPusherWorker::class.java.name -> + AddPusherWorker::class.java.name -> AddPusherWorker(appContext, workerParameters, sessionManager) - GetGroupDataWorker::class.java.name -> + GetGroupDataWorker::class.java.name -> GetGroupDataWorker(appContext, workerParameters, sessionManager) MultipleEventSendingDispatcherWorker::class.java.name -> MultipleEventSendingDispatcherWorker(appContext, workerParameters, sessionManager) - RedactEventWorker::class.java.name -> + RedactEventWorker::class.java.name -> RedactEventWorker(appContext, workerParameters, sessionManager) - SendEventWorker::class.java.name -> + SendEventWorker::class.java.name -> SendEventWorker(appContext, workerParameters, sessionManager) - SyncWorker::class.java.name -> + SyncWorker::class.java.name -> SyncWorker(appContext, workerParameters, sessionManager) - UpdateTrustWorker::class.java.name -> + UpdateTrustWorker::class.java.name -> UpdateTrustWorker(appContext, workerParameters, sessionManager) - UploadContentWorker::class.java.name -> + UploadContentWorker::class.java.name -> UploadContentWorker(appContext, workerParameters, sessionManager) - else -> { + DeactivateLiveLocationShareWorker::class.java.name -> + DeactivateLiveLocationShareWorker(appContext, workerParameters, sessionManager) + else -> { Timber.w("No worker defined on MatrixWorkerFactory for $workerClassName will delegate to default.") // Return null to delegate to the default WorkerFactory. null @@ -78,9 +81,11 @@ internal class MatrixWorkerFactory @Inject constructor(private val sessionManage * This worker is launched by the factory with the isCreatedByMatrixWorkerFactory flag to true. * If the MatrixWorkerFactory is not set up, it will default to the other constructor and it will throw */ - class CheckFactoryWorker(context: Context, - workerParameters: WorkerParameters, - private val isCreatedByMatrixWorkerFactory: Boolean) : + class CheckFactoryWorker( + context: Context, + workerParameters: WorkerParameters, + private val isCreatedByMatrixWorkerFactory: Boolean + ) : CoroutineWorker(context, workerParameters) { // Called by WorkManager if there is no MatrixWorkerFactory diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/userstories/Us000InitMatrix.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/userstories/Us000InitMatrix.kt new file mode 100644 index 0000000000..06c6cc9ef9 --- /dev/null +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/userstories/Us000InitMatrix.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. + */ + +@file:Suppress("unused") + +package org.matrix.android.sdk.userstories + +/** + * ### Title + * Init a Matrix object. + * + * ### Required APIs: + * - [org.matrix.android.sdk.api.Matrix] constructor + */ +class Us000InitMatrix private constructor() diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/userstories/Us100SignIn.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/userstories/Us100SignIn.kt new file mode 100644 index 0000000000..f508a75db7 --- /dev/null +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/userstories/Us100SignIn.kt @@ -0,0 +1,31 @@ +/* + * 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. + */ + +@file:Suppress("unused") + +package org.matrix.android.sdk.userstories + +/** + * ### Title + * Sign in to an existing account. + * + * #### Required APIs: + * - [org.matrix.android.sdk.api.Matrix.authenticationService] + * - [org.matrix.android.sdk.api.auth.AuthenticationService.getLoginFlow] + * - [org.matrix.android.sdk.api.auth.AuthenticationService.getLoginWizard] + * - [org.matrix.android.sdk.api.auth.login.LoginWizard.login] + */ +class Us100SignIn private constructor() diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/userstories/Us150VerifySession.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/userstories/Us150VerifySession.kt new file mode 100644 index 0000000000..ac56cc35dd --- /dev/null +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/userstories/Us150VerifySession.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. + */ + +@file:Suppress("unused") + +package org.matrix.android.sdk.userstories + +/** + * ### Title + * Verify a Session after a Sign in. + * + * #### Required APIs: + * - TODO + */ +class Us150VerifySession private constructor() diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/userstories/Us190SignOut.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/userstories/Us190SignOut.kt new file mode 100644 index 0000000000..0c74c0dfef --- /dev/null +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/userstories/Us190SignOut.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. + */ + +@file:Suppress("unused") + +package org.matrix.android.sdk.userstories + +/** + * ### Title + * Sign out. + * + * #### Required APIs: + * - TODO + */ +class Us190SignOut private constructor() diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/userstories/Us200RoomList.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/userstories/Us200RoomList.kt new file mode 100644 index 0000000000..1f0b2e4103 --- /dev/null +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/userstories/Us200RoomList.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. + */ + +@file:Suppress("unused") + +package org.matrix.android.sdk.userstories + +/** + * ### Title + * Get the Room list. + * + * #### Required APIs: + * - TODO + */ +class Us200RoomList private constructor() diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/userstories/Us300RoomTimeline.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/userstories/Us300RoomTimeline.kt new file mode 100644 index 0000000000..5db06f95f6 --- /dev/null +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/userstories/Us300RoomTimeline.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. + */ + +@file:Suppress("unused") + +package org.matrix.android.sdk.userstories + +/** + * ### Title + * Display a Room timeline, and navigate backward and forward. + * + * #### Required APIs: + * - TODO + */ +class Us300RoomTimeline private constructor() diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/userstories/Us350RoomTimelineFromPermalink.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/userstories/Us350RoomTimelineFromPermalink.kt new file mode 100644 index 0000000000..5e0f6d3b3d --- /dev/null +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/userstories/Us350RoomTimelineFromPermalink.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. + */ + +@file:Suppress("unused") + +package org.matrix.android.sdk.userstories + +/** + * ### Title + * Display a Room timeline at a specific point. + * + * #### Required APIs: + * - TODO + */ +class Us350RoomTimelineFromPermalink private constructor() diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/userstories/Us400RoomSendContent.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/userstories/Us400RoomSendContent.kt new file mode 100644 index 0000000000..3f72cd0b9e --- /dev/null +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/userstories/Us400RoomSendContent.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. + */ + +@file:Suppress("unused") + +package org.matrix.android.sdk.userstories + +/** + * ### Title + * Send content to a room, including monitoring the sending state. + * + * #### Required APIs: + * - TODO + */ +class Us400RoomSendContent private constructor() diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/userstories/Us500Notification.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/userstories/Us500Notification.kt new file mode 100644 index 0000000000..c5c2107fa6 --- /dev/null +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/userstories/Us500Notification.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. + */ + +@file:Suppress("unused") + +package org.matrix.android.sdk.userstories + +/** + * ### Title + * Get notified when new Events are received. + * + * #### Required APIs: + * - TODO + */ +class Us500Notification private constructor() diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/userstories/Us600SyncWithTheServer.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/userstories/Us600SyncWithTheServer.kt new file mode 100644 index 0000000000..6b8fb87df7 --- /dev/null +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/userstories/Us600SyncWithTheServer.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. + */ + +@file:Suppress("unused") + +package org.matrix.android.sdk.userstories + +/** + * ### Title + * Manage the sync with the server. + * + * #### Required APIs: + * - TODO + */ +class Us600SyncWithTheServer private constructor() diff --git a/matrix-sdk-android/src/test/java/org/matrix/android/sdk/api/MatrixPatternsTest.kt b/matrix-sdk-android/src/test/java/org/matrix/android/sdk/api/MatrixPatternsTest.kt new file mode 100644 index 0000000000..0d0450adc2 --- /dev/null +++ b/matrix-sdk-android/src/test/java/org/matrix/android/sdk/api/MatrixPatternsTest.kt @@ -0,0 +1,40 @@ +/* + * 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 + +import org.amshove.kluent.shouldBeEqualTo +import org.junit.Test + +class MatrixPatternsTest { + + @Test + fun `given user id cases, when checking isUserId, then returns expected`() { + val cases = listOf( + UserIdCase("foobar", isUserId = false), + UserIdCase("@foobar", isUserId = false), + UserIdCase("foobar@matrix.org", isUserId = false), + UserIdCase("@foobar: matrix.org", isUserId = false), + UserIdCase("@foobar:matrix.org", isUserId = true), + ) + + cases.forEach { (input, expected) -> + MatrixPatterns.isUserId(input) shouldBeEqualTo expected + } + } +} + +private data class UserIdCase(val input: String, val isUserId: Boolean) diff --git a/matrix-sdk-android/src/test/java/org/matrix/android/sdk/internal/auth/version/HomeServerVersionTest.kt b/matrix-sdk-android/src/test/java/org/matrix/android/sdk/internal/auth/version/HomeServerVersionTest.kt new file mode 100644 index 0000000000..413af9c434 --- /dev/null +++ b/matrix-sdk-android/src/test/java/org/matrix/android/sdk/internal/auth/version/HomeServerVersionTest.kt @@ -0,0 +1,57 @@ +/* + * Copyright 2022 The Matrix.org Foundation C.I.C. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.matrix.android.sdk.internal.auth.version + +import org.amshove.kluent.internal.assertEquals +import org.junit.Test + +class HomeServerVersionTest { + + @Test + fun `given a semantic version, when parsing, then converts to home server version`() { + val supportedVersions = listOf( + case("1.5", expected = aVersion(1, 5, 0)), + case("0.5.1", expected = aVersion(0, 5, 1)), + case("1.0.0", expected = aVersion(1, 0, 0)), + case("1.10.3", expected = aVersion(1, 10, 3)), + ).withPrefixes("v", "r") + + val unsupportedVersions = listOf( + case("v-1.5.1", expected = null), + case("1.4.", expected = null), + case("1.5.1.", expected = null), + case("r1", expected = null), + case("a", expected = null), + case("1a.2b.3c", expected = null), + case("r", expected = null), + ) + + (supportedVersions + unsupportedVersions).forEach { (input, expected) -> + val result = HomeServerVersion.parse(input) + + assertEquals(expected, result, "Expected $input to be $expected but got $result") + } + } +} + +private fun aVersion(major: Int, minor: Int, patch: Int) = HomeServerVersion(major, minor, patch) +private fun case(input: String, expected: HomeServerVersion?) = Case(input, expected) + +private fun List.withPrefixes(vararg prefixes: String) = map { case -> + prefixes.map { prefix -> case.copy(input = "$prefix${case.input}") } +}.flatten() + +private data class Case(val input: String, val expected: HomeServerVersion?) diff --git a/matrix-sdk-android/src/test/java/org/matrix/android/sdk/internal/database/mapper/LiveLocationShareAggregatedSummaryMapperTest.kt b/matrix-sdk-android/src/test/java/org/matrix/android/sdk/internal/database/mapper/LiveLocationShareAggregatedSummaryMapperTest.kt new file mode 100644 index 0000000000..47d5f46525 --- /dev/null +++ b/matrix-sdk-android/src/test/java/org/matrix/android/sdk/internal/database/mapper/LiveLocationShareAggregatedSummaryMapperTest.kt @@ -0,0 +1,56 @@ +/* + * 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 com.squareup.moshi.Moshi +import org.amshove.kluent.shouldBeEqualTo +import org.junit.Test +import org.matrix.android.sdk.api.session.room.model.livelocation.LiveLocationShareAggregatedSummary +import org.matrix.android.sdk.api.session.room.model.message.LocationInfo +import org.matrix.android.sdk.api.session.room.model.message.MessageBeaconLocationDataContent +import org.matrix.android.sdk.internal.database.model.livelocation.LiveLocationShareAggregatedSummaryEntity + +private const val ANY_USER_ID = "a-user-id" +private const val ANY_ACTIVE_STATE = true +private const val ANY_TIMEOUT = 123L +private val A_LOCATION_INFO = LocationInfo("a-geo-uri") + +class LiveLocationShareAggregatedSummaryMapperTest { + + private val mapper = LiveLocationShareAggregatedSummaryMapper() + + @Test + fun `given an entity then result should be mapped correctly`() { + val entity = anEntity(content = MessageBeaconLocationDataContent(locationInfo = A_LOCATION_INFO)) + + val summary = mapper.map(entity) + + summary shouldBeEqualTo LiveLocationShareAggregatedSummary( + userId = ANY_USER_ID, + isActive = ANY_ACTIVE_STATE, + endOfLiveTimestampMillis = ANY_TIMEOUT, + lastLocationDataContent = MessageBeaconLocationDataContent(locationInfo = A_LOCATION_INFO) + ) + } + + private fun anEntity(content: MessageBeaconLocationDataContent) = LiveLocationShareAggregatedSummaryEntity( + userId = ANY_USER_ID, + isActive = ANY_ACTIVE_STATE, + endOfLiveTimestampMillis = ANY_TIMEOUT, + lastLocationContent = Moshi.Builder().build().adapter(MessageBeaconLocationDataContent::class.java).toJson(content) + ) +} diff --git a/matrix-sdk-android/src/test/java/org/matrix/android/sdk/internal/session/pushers/DefaultAddPusherTaskTest.kt b/matrix-sdk-android/src/test/java/org/matrix/android/sdk/internal/session/pushers/DefaultAddPusherTaskTest.kt index 32b1d44fb9..dac33069f3 100644 --- a/matrix-sdk-android/src/test/java/org/matrix/android/sdk/internal/session/pushers/DefaultAddPusherTaskTest.kt +++ b/matrix-sdk-android/src/test/java/org/matrix/android/sdk/internal/session/pushers/DefaultAddPusherTaskTest.kt @@ -23,10 +23,12 @@ import org.amshove.kluent.shouldBeEqualTo import org.junit.Test import org.matrix.android.sdk.api.session.pushers.PusherState import org.matrix.android.sdk.internal.database.model.PusherEntity +import org.matrix.android.sdk.internal.database.model.PusherEntityFields import org.matrix.android.sdk.test.fakes.FakeGlobalErrorReceiver import org.matrix.android.sdk.test.fakes.FakeMonarchy import org.matrix.android.sdk.test.fakes.FakePushersAPI import org.matrix.android.sdk.test.fakes.FakeRequestExecutor +import org.matrix.android.sdk.test.fakes.givenEqualTo import java.net.SocketException private val A_JSON_PUSHER = JsonPusher( @@ -56,6 +58,7 @@ class DefaultAddPusherTaskTest { @Test fun `given no persisted pusher when adding Pusher then updates api and inserts result with Registered state`() { monarchy.givenWhereReturns(result = null) + .givenEqualTo(PusherEntityFields.PUSH_KEY, A_JSON_PUSHER.pushKey) runTest { addPusherTask.execute(AddPusherTask.Params(A_JSON_PUSHER)) } @@ -71,6 +74,7 @@ class DefaultAddPusherTaskTest { fun `given a persisted pusher when adding Pusher then updates api and mutates persisted result with Registered state`() { val realmResult = PusherEntity(appDisplayName = null) monarchy.givenWhereReturns(result = realmResult) + .givenEqualTo(PusherEntityFields.PUSH_KEY, A_JSON_PUSHER.pushKey) runTest { addPusherTask.execute(AddPusherTask.Params(A_JSON_PUSHER)) } @@ -84,6 +88,7 @@ class DefaultAddPusherTaskTest { fun `given a persisted push entity and SetPush API fails when adding Pusher then mutates persisted result with Failed registration state and rethrows`() { val realmResult = PusherEntity() monarchy.givenWhereReturns(result = realmResult) + .givenEqualTo(PusherEntityFields.PUSH_KEY, A_JSON_PUSHER.pushKey) pushersAPI.givenSetPusherErrors(SocketException()) assertFailsWith { @@ -96,6 +101,7 @@ class DefaultAddPusherTaskTest { @Test fun `given no persisted push entity and SetPush API fails when adding Pusher then rethrows error`() { monarchy.givenWhereReturns(result = null) + .givenEqualTo(PusherEntityFields.PUSH_KEY, A_JSON_PUSHER.pushKey) pushersAPI.givenSetPusherErrors(SocketException()) assertFailsWith { diff --git a/matrix-sdk-android/src/test/java/org/matrix/android/sdk/internal/session/room/aggregation/livelocation/LiveLocationAggregationProcessorTest.kt b/matrix-sdk-android/src/test/java/org/matrix/android/sdk/internal/session/room/aggregation/livelocation/LiveLocationAggregationProcessorTest.kt new file mode 100644 index 0000000000..933087af2b --- /dev/null +++ b/matrix-sdk-android/src/test/java/org/matrix/android/sdk/internal/session/room/aggregation/livelocation/LiveLocationAggregationProcessorTest.kt @@ -0,0 +1,405 @@ +/* + * 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.aggregation.livelocation + +import androidx.work.ExistingWorkPolicy +import org.amshove.kluent.shouldBeEqualTo +import org.junit.Test +import org.matrix.android.sdk.api.session.events.model.Event +import org.matrix.android.sdk.api.session.events.model.UnsignedData +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.message.LocationInfo +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.livelocation.LiveLocationShareAggregatedSummaryEntity +import org.matrix.android.sdk.internal.database.model.livelocation.LiveLocationShareAggregatedSummaryEntityFields +import org.matrix.android.sdk.test.fakes.FakeClock +import org.matrix.android.sdk.test.fakes.FakeRealm +import org.matrix.android.sdk.test.fakes.FakeWorkManagerProvider +import org.matrix.android.sdk.test.fakes.givenEqualTo +import org.matrix.android.sdk.test.fakes.givenFindAll +import org.matrix.android.sdk.test.fakes.givenFindFirst +import org.matrix.android.sdk.test.fakes.givenNotEqualTo + +private const val A_SESSION_ID = "session_id" +private const val A_SENDER_ID = "sender_id" +private const val AN_EVENT_ID = "event_id" +private const val A_ROOM_ID = "room_id" +private const val A_TIMESTAMP = 1654689143L +private const val A_TIMEOUT_MILLIS = 15 * 60 * 1000L +private const val A_LATITUDE = 40.05 +private const val A_LONGITUDE = 29.24 +private const val A_UNCERTAINTY = 30.0 +private const val A_GEO_URI = "geo:$A_LATITUDE,$A_LONGITUDE;u=$A_UNCERTAINTY" + +internal class LiveLocationAggregationProcessorTest { + + private val fakeWorkManagerProvider = FakeWorkManagerProvider() + private val fakeClock = FakeClock() + private val fakeRealm = FakeRealm() + private val fakeQuery = fakeRealm.givenWhere() + + private val liveLocationAggregationProcessor = LiveLocationAggregationProcessor( + sessionId = A_SESSION_ID, + workManagerProvider = fakeWorkManagerProvider.instance, + clock = fakeClock + ) + + @Test + fun `given beacon info when it is local echo then it is ignored`() { + val event = Event(senderId = A_SENDER_ID) + val beaconInfo = MessageBeaconInfoContent() + + val result = liveLocationAggregationProcessor.handleBeaconInfo( + realm = fakeRealm.instance, + event = event, + content = beaconInfo, + roomId = A_ROOM_ID, + isLocalEcho = true + ) + + result shouldBeEqualTo false + } + + private data class IgnoredBeaconInfoEvent( + val event: Event, + val beaconInfo: MessageBeaconInfoContent + ) + + @Test + fun `given beacon info and event when some values are missing then it is ignored`() { + val ignoredInfoEvents = listOf( + // missing senderId + IgnoredBeaconInfoEvent( + event = Event(eventId = AN_EVENT_ID, senderId = null), + beaconInfo = MessageBeaconInfoContent() + ), + // empty senderId + IgnoredBeaconInfoEvent( + event = Event(eventId = AN_EVENT_ID, senderId = ""), + beaconInfo = MessageBeaconInfoContent() + ), + // beacon is live and no eventId + IgnoredBeaconInfoEvent( + event = Event(eventId = null, senderId = A_SENDER_ID), + beaconInfo = MessageBeaconInfoContent(isLive = true) + ), + // beacon is live and eventId is empty + IgnoredBeaconInfoEvent( + event = Event(eventId = "", senderId = A_SENDER_ID), + beaconInfo = MessageBeaconInfoContent(isLive = true) + ), + // beacon is not live and replaced event id is null + IgnoredBeaconInfoEvent( + event = Event( + eventId = AN_EVENT_ID, + senderId = A_SENDER_ID, + unsignedData = UnsignedData( + age = 123, + replacesState = null + ) + ), + beaconInfo = MessageBeaconInfoContent(isLive = false) + ), + // beacon is not live and replaced event id is empty + IgnoredBeaconInfoEvent( + event = Event( + eventId = AN_EVENT_ID, + senderId = A_SENDER_ID, + unsignedData = UnsignedData( + age = 123, + replacesState = "" + ) + ), + beaconInfo = MessageBeaconInfoContent(isLive = false) + ), + ) + + ignoredInfoEvents.forEach { + val result = liveLocationAggregationProcessor.handleBeaconInfo( + realm = fakeRealm.instance, + event = it.event, + content = it.beaconInfo, + roomId = A_ROOM_ID, + isLocalEcho = false + ) + + result shouldBeEqualTo false + } + } + + @Test + fun `given beacon info and existing entity when beacon content is correct and active then it is aggregated`() { + val event = Event( + senderId = A_SENDER_ID, + eventId = AN_EVENT_ID + ) + val beaconInfo = MessageBeaconInfoContent( + isLive = true, + unstableTimestampMillis = A_TIMESTAMP, + timeout = A_TIMEOUT_MILLIS + ) + fakeClock.givenEpoch(A_TIMESTAMP + 5000) + fakeWorkManagerProvider.fakeWorkManager.expectEnqueueUniqueWork() + val aggregatedEntity = givenLastSummaryQueryReturns(eventId = AN_EVENT_ID, roomId = A_ROOM_ID) + val previousEntities = givenActiveSummaryListQueryReturns( + listOf( + LiveLocationShareAggregatedSummaryEntity( + eventId = "${AN_EVENT_ID}1", + roomId = A_ROOM_ID, + userId = A_SENDER_ID, + isActive = true + ) + ) + ) + + val result = liveLocationAggregationProcessor.handleBeaconInfo( + realm = fakeRealm.instance, + event = event, + content = beaconInfo, + roomId = A_ROOM_ID, + isLocalEcho = false + ) + + result shouldBeEqualTo true + aggregatedEntity.eventId shouldBeEqualTo AN_EVENT_ID + aggregatedEntity.roomId shouldBeEqualTo A_ROOM_ID + aggregatedEntity.userId shouldBeEqualTo A_SENDER_ID + aggregatedEntity.isActive shouldBeEqualTo true + aggregatedEntity.endOfLiveTimestampMillis shouldBeEqualTo A_TIMESTAMP + A_TIMEOUT_MILLIS + aggregatedEntity.lastLocationContent shouldBeEqualTo null + previousEntities.forEach { entity -> + entity.isActive shouldBeEqualTo false + } + fakeWorkManagerProvider.fakeWorkManager.verifyEnqueueUniqueWork( + workName = DeactivateLiveLocationShareWorker.getWorkName(eventId = AN_EVENT_ID, roomId = A_ROOM_ID), + policy = ExistingWorkPolicy.REPLACE + ) + } + + @Test + fun `given beacon info and existing entity when beacon content is correct and inactive then it is aggregated`() { + val unsignedData = UnsignedData( + age = 123, + replacesState = AN_EVENT_ID + ) + val event = Event( + senderId = A_SENDER_ID, + eventId = "", + unsignedData = unsignedData + ) + val beaconInfo = MessageBeaconInfoContent( + isLive = false, + unstableTimestampMillis = A_TIMESTAMP, + timeout = A_TIMEOUT_MILLIS + ) + fakeClock.givenEpoch(A_TIMESTAMP + 5000) + fakeWorkManagerProvider.fakeWorkManager.expectCancelUniqueWork() + val aggregatedEntity = givenLastSummaryQueryReturns(eventId = AN_EVENT_ID, roomId = A_ROOM_ID) + val previousEntities = givenActiveSummaryListQueryReturns( + listOf( + LiveLocationShareAggregatedSummaryEntity( + eventId = "${AN_EVENT_ID}1", + roomId = A_ROOM_ID, + userId = A_SENDER_ID, + isActive = true + ) + ) + + ) + + val result = liveLocationAggregationProcessor.handleBeaconInfo( + realm = fakeRealm.instance, + event = event, + content = beaconInfo, + roomId = A_ROOM_ID, + isLocalEcho = false + ) + + result shouldBeEqualTo true + aggregatedEntity.eventId shouldBeEqualTo AN_EVENT_ID + aggregatedEntity.roomId shouldBeEqualTo A_ROOM_ID + aggregatedEntity.userId shouldBeEqualTo A_SENDER_ID + aggregatedEntity.isActive shouldBeEqualTo false + aggregatedEntity.endOfLiveTimestampMillis shouldBeEqualTo A_TIMESTAMP + A_TIMEOUT_MILLIS + aggregatedEntity.lastLocationContent shouldBeEqualTo null + previousEntities.forEach { entity -> + entity.isActive shouldBeEqualTo false + } + fakeWorkManagerProvider.fakeWorkManager.verifyCancelUniqueWork( + workName = DeactivateLiveLocationShareWorker.getWorkName(eventId = AN_EVENT_ID, roomId = A_ROOM_ID) + ) + } + + @Test + fun `given beacon location data when it is local echo then it is ignored`() { + val event = Event(senderId = A_SENDER_ID) + val beaconLocationData = MessageBeaconLocationDataContent() + + val result = liveLocationAggregationProcessor.handleBeaconLocationData( + realm = fakeRealm.instance, + event = event, + content = beaconLocationData, + roomId = A_ROOM_ID, + relatedEventId = AN_EVENT_ID, + isLocalEcho = true + ) + + result shouldBeEqualTo false + } + + private data class IgnoredBeaconLocationDataEvent( + val event: Event, + val beaconLocationData: MessageBeaconLocationDataContent + ) + + @Test + fun `given event and beacon location data when some values are missing then it is ignored`() { + val ignoredLocationDataEvents = listOf( + // missing sender id + IgnoredBeaconLocationDataEvent( + event = Event(eventId = AN_EVENT_ID), + beaconLocationData = MessageBeaconLocationDataContent() + ), + // empty sender id + IgnoredBeaconLocationDataEvent( + event = Event(eventId = AN_EVENT_ID, senderId = ""), + beaconLocationData = MessageBeaconLocationDataContent() + ), + ) + + ignoredLocationDataEvents.forEach { + val result = liveLocationAggregationProcessor.handleBeaconLocationData( + realm = fakeRealm.instance, + event = it.event, + content = it.beaconLocationData, + roomId = A_ROOM_ID, + relatedEventId = "", + isLocalEcho = false + ) + result shouldBeEqualTo false + } + } + + @Test + fun `given beacon location data when relatedEventId is null or empty then it is ignored`() { + val event = Event(senderId = A_SENDER_ID) + val beaconLocationData = MessageBeaconLocationDataContent() + + listOf(null, "").forEach { + val result = liveLocationAggregationProcessor.handleBeaconLocationData( + realm = fakeRealm.instance, + event = event, + content = beaconLocationData, + roomId = A_ROOM_ID, + relatedEventId = it, + isLocalEcho = false + ) + result shouldBeEqualTo false + } + } + + @Test + fun `given beacon location data when location is less recent than the saved one then it is ignored`() { + val event = Event(eventId = AN_EVENT_ID, senderId = A_SENDER_ID) + val beaconLocationData = MessageBeaconLocationDataContent( + unstableTimestampMillis = A_TIMESTAMP - 60_000 + ) + val lastBeaconLocationContent = MessageBeaconLocationDataContent( + unstableTimestampMillis = A_TIMESTAMP + ) + givenLastSummaryQueryReturns( + eventId = AN_EVENT_ID, + roomId = A_ROOM_ID, + beaconLocationContent = lastBeaconLocationContent + ) + + val result = liveLocationAggregationProcessor.handleBeaconLocationData( + realm = fakeRealm.instance, + event = event, + content = beaconLocationData, + roomId = A_ROOM_ID, + relatedEventId = AN_EVENT_ID, + isLocalEcho = false + ) + + result shouldBeEqualTo false + } + + @Test + fun `given beacon location data when location is more recent than the saved one then it is aggregated`() { + val event = Event(eventId = AN_EVENT_ID, senderId = A_SENDER_ID) + val locationInfo = LocationInfo(geoUri = A_GEO_URI) + val beaconLocationData = MessageBeaconLocationDataContent( + unstableTimestampMillis = A_TIMESTAMP, + unstableLocationInfo = locationInfo + ) + val lastBeaconLocationContent = MessageBeaconLocationDataContent( + unstableTimestampMillis = A_TIMESTAMP - 60_000 + ) + val entity = givenLastSummaryQueryReturns( + eventId = AN_EVENT_ID, + roomId = A_ROOM_ID, + beaconLocationContent = lastBeaconLocationContent + ) + + val result = liveLocationAggregationProcessor.handleBeaconLocationData( + realm = fakeRealm.instance, + event = event, + content = beaconLocationData, + roomId = A_ROOM_ID, + relatedEventId = AN_EVENT_ID, + isLocalEcho = false + ) + + result shouldBeEqualTo true + val savedLocationData = ContentMapper.map(entity.lastLocationContent).toModel() + savedLocationData?.getBestTimestampMillis() shouldBeEqualTo A_TIMESTAMP + savedLocationData?.getBestLocationInfo()?.geoUri shouldBeEqualTo A_GEO_URI + } + + private fun givenLastSummaryQueryReturns( + eventId: String, + roomId: String, + beaconLocationContent: MessageBeaconLocationDataContent? = null + ): LiveLocationShareAggregatedSummaryEntity { + val result = LiveLocationShareAggregatedSummaryEntity( + eventId = eventId, + roomId = roomId, + lastLocationContent = ContentMapper.map(beaconLocationContent?.toContent()) + ) + fakeQuery + .givenEqualTo(LiveLocationShareAggregatedSummaryEntityFields.EVENT_ID, eventId) + .givenEqualTo(LiveLocationShareAggregatedSummaryEntityFields.ROOM_ID, roomId) + .givenFindFirst(result) + return result + } + + private fun givenActiveSummaryListQueryReturns( + summaryList: List + ): List { + fakeQuery + .givenEqualTo(LiveLocationShareAggregatedSummaryEntityFields.ROOM_ID, A_ROOM_ID) + .givenNotEqualTo(LiveLocationShareAggregatedSummaryEntityFields.EVENT_ID, AN_EVENT_ID) + .givenEqualTo(LiveLocationShareAggregatedSummaryEntityFields.USER_ID, A_SENDER_ID) + .givenEqualTo(LiveLocationShareAggregatedSummaryEntityFields.IS_ACTIVE, true) + .givenFindAll(summaryList) + return summaryList + } +} diff --git a/matrix-sdk-android/src/test/java/org/matrix/android/sdk/internal/session/room/aggregation/poll/PollAggregationProcessorTest.kt b/matrix-sdk-android/src/test/java/org/matrix/android/sdk/internal/session/room/aggregation/poll/PollAggregationProcessorTest.kt index 837bbeea26..3044ca5d43 100644 --- a/matrix-sdk-android/src/test/java/org/matrix/android/sdk/internal/session/room/aggregation/poll/PollAggregationProcessorTest.kt +++ b/matrix-sdk-android/src/test/java/org/matrix/android/sdk/internal/session/room/aggregation/poll/PollAggregationProcessorTest.kt @@ -19,8 +19,6 @@ package org.matrix.android.sdk.internal.session.room.aggregation.poll import io.mockk.every import io.mockk.mockk import io.realm.RealmList -import io.realm.RealmModel -import io.realm.RealmQuery import org.amshove.kluent.shouldBeFalse import org.amshove.kluent.shouldBeTrue import org.junit.Before @@ -46,6 +44,8 @@ import org.matrix.android.sdk.internal.session.room.aggregation.poll.PollEventsT import org.matrix.android.sdk.internal.session.room.aggregation.poll.PollEventsTestData.A_TIMELINE_EVENT import org.matrix.android.sdk.internal.session.room.aggregation.poll.PollEventsTestData.A_USER_ID_1 import org.matrix.android.sdk.test.fakes.FakeRealm +import org.matrix.android.sdk.test.fakes.givenEqualTo +import org.matrix.android.sdk.test.fakes.givenFindFirst class PollAggregationProcessorTest { @@ -135,14 +135,11 @@ class PollAggregationProcessorTest { pollAggregationProcessor.handlePollEndEvent(session, powerLevelsHelper, realm.instance, event).shouldBeFalse() } - private inline fun RealmQuery.givenEqualTo(fieldName: String, value: String, result: RealmQuery) { - every { equalTo(fieldName, value) } returns result - } - private fun mockEventAnnotationsSummaryEntity() { - val queryResult = realm.givenWhereReturns(result = EventAnnotationsSummaryEntity()) - queryResult.givenEqualTo(EventAnnotationsSummaryEntityFields.ROOM_ID, A_POLL_REPLACE_EVENT.roomId!!, queryResult) - queryResult.givenEqualTo(EventAnnotationsSummaryEntityFields.EVENT_ID, A_POLL_REPLACE_EVENT.eventId!!, queryResult) + realm.givenWhere() + .givenFindFirst(EventAnnotationsSummaryEntity()) + .givenEqualTo(EventAnnotationsSummaryEntityFields.ROOM_ID, A_POLL_REPLACE_EVENT.roomId!!) + .givenEqualTo(EventAnnotationsSummaryEntityFields.EVENT_ID, A_POLL_REPLACE_EVENT.eventId!!) } private fun mockRoom( diff --git a/matrix-sdk-android/src/test/java/org/matrix/android/sdk/internal/session/room/location/DefaultCheckIfExistingActiveLiveTaskTest.kt b/matrix-sdk-android/src/test/java/org/matrix/android/sdk/internal/session/room/location/DefaultCheckIfExistingActiveLiveTaskTest.kt new file mode 100644 index 0000000000..3198392eab --- /dev/null +++ b/matrix-sdk-android/src/test/java/org/matrix/android/sdk/internal/session/room/location/DefaultCheckIfExistingActiveLiveTaskTest.kt @@ -0,0 +1,105 @@ +/* + * 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.location + +import io.mockk.unmockkAll +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.test.runTest +import org.amshove.kluent.shouldBeEqualTo +import org.junit.After +import org.junit.Test +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.model.message.MessageBeaconInfoContent +import org.matrix.android.sdk.test.fakes.FakeGetActiveBeaconInfoForUserTask + +private const val A_USER_ID = "user-id" +private const val A_ROOM_ID = "room-id" +private const val A_TIMEOUT = 15_000L +private const val AN_EPOCH = 1655210176L + +@ExperimentalCoroutinesApi +class DefaultCheckIfExistingActiveLiveTaskTest { + + private val fakeGetActiveBeaconInfoForUserTask = FakeGetActiveBeaconInfoForUserTask() + + private val defaultCheckIfExistingActiveLiveTask = DefaultCheckIfExistingActiveLiveTask( + getActiveBeaconInfoForUserTask = fakeGetActiveBeaconInfoForUserTask + ) + + @After + fun tearDown() { + unmockkAll() + } + + @Test + fun `given parameters and existing active live event when calling the task then result is true`() = runTest { + val params = CheckIfExistingActiveLiveTask.Params( + roomId = A_ROOM_ID + ) + val currentStateEvent = Event( + stateKey = A_USER_ID, + content = MessageBeaconInfoContent( + timeout = A_TIMEOUT, + isLive = true, + unstableTimestampMillis = AN_EPOCH + ).toContent() + ) + fakeGetActiveBeaconInfoForUserTask.givenExecuteReturns(currentStateEvent) + + val result = defaultCheckIfExistingActiveLiveTask.execute(params) + + result shouldBeEqualTo true + val expectedGetActiveBeaconParams = GetActiveBeaconInfoForUserTask.Params( + roomId = params.roomId + ) + fakeGetActiveBeaconInfoForUserTask.verifyExecute(expectedGetActiveBeaconParams) + } + + @Test + fun `given parameters and no existing active live event when calling the task then result is false`() = runTest { + val params = CheckIfExistingActiveLiveTask.Params( + roomId = A_ROOM_ID + ) + val inactiveEvents = listOf( + // no event + null, + // null content + Event( + stateKey = A_USER_ID, + content = null + ), + // inactive live + Event( + stateKey = A_USER_ID, + content = MessageBeaconInfoContent( + timeout = A_TIMEOUT, + isLive = false, + unstableTimestampMillis = AN_EPOCH + ).toContent() + ) + ) + + inactiveEvents.forEach { currentStateEvent -> + fakeGetActiveBeaconInfoForUserTask.givenExecuteReturns(currentStateEvent) + + val result = defaultCheckIfExistingActiveLiveTask.execute(params) + + result shouldBeEqualTo false + } + } +} diff --git a/matrix-sdk-android/src/test/java/org/matrix/android/sdk/internal/session/room/location/DefaultGetActiveBeaconInfoForUserTaskTest.kt b/matrix-sdk-android/src/test/java/org/matrix/android/sdk/internal/session/room/location/DefaultGetActiveBeaconInfoForUserTaskTest.kt new file mode 100644 index 0000000000..588bfaa979 --- /dev/null +++ b/matrix-sdk-android/src/test/java/org/matrix/android/sdk/internal/session/room/location/DefaultGetActiveBeaconInfoForUserTaskTest.kt @@ -0,0 +1,75 @@ +/* + * 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.location + +import io.mockk.unmockkAll +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.test.runTest +import org.amshove.kluent.shouldBeEqualTo +import org.junit.After +import org.junit.Test +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.room.model.message.MessageBeaconInfoContent +import org.matrix.android.sdk.test.fakes.FakeStateEventDataSource + +private const val A_USER_ID = "user-id" +private const val A_ROOM_ID = "room-id" +private const val A_TIMEOUT = 15_000L +private const val AN_EPOCH = 1655210176L + +@ExperimentalCoroutinesApi +class DefaultGetActiveBeaconInfoForUserTaskTest { + + private val fakeStateEventDataSource = FakeStateEventDataSource() + + private val defaultGetActiveBeaconInfoForUserTask = DefaultGetActiveBeaconInfoForUserTask( + userId = A_USER_ID, + stateEventDataSource = fakeStateEventDataSource.instance + ) + + @After + fun tearDown() { + unmockkAll() + } + + @Test + fun `given parameters and no error when calling the task then result is computed`() = runTest { + val currentStateEvent = Event( + stateKey = A_USER_ID, + content = MessageBeaconInfoContent( + timeout = A_TIMEOUT, + isLive = true, + unstableTimestampMillis = AN_EPOCH + ).toContent() + ) + fakeStateEventDataSource.givenGetStateEventReturns(currentStateEvent) + val params = GetActiveBeaconInfoForUserTask.Params( + roomId = A_ROOM_ID + ) + + val result = defaultGetActiveBeaconInfoForUserTask.execute(params) + + result shouldBeEqualTo currentStateEvent + fakeStateEventDataSource.verifyGetStateEvent( + roomId = params.roomId, + eventType = EventType.STATE_ROOM_BEACON_INFO.first(), + stateKey = A_USER_ID + ) + } +} diff --git a/matrix-sdk-android/src/test/java/org/matrix/android/sdk/internal/session/room/location/DefaultLocationSharingServiceTest.kt b/matrix-sdk-android/src/test/java/org/matrix/android/sdk/internal/session/room/location/DefaultLocationSharingServiceTest.kt new file mode 100644 index 0000000000..de91206531 --- /dev/null +++ b/matrix-sdk-android/src/test/java/org/matrix/android/sdk/internal/session/room/location/DefaultLocationSharingServiceTest.kt @@ -0,0 +1,268 @@ +/* + * 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.location + +import androidx.arch.core.util.Function +import androidx.lifecycle.MutableLiveData +import androidx.lifecycle.Transformations +import io.mockk.coEvery +import io.mockk.coVerify +import io.mockk.every +import io.mockk.mockk +import io.mockk.mockkStatic +import io.mockk.slot +import io.mockk.unmockkAll +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.test.runTest +import org.amshove.kluent.shouldBeEqualTo +import org.junit.After +import org.junit.Before +import org.junit.Test +import org.matrix.android.sdk.api.session.room.location.UpdateLiveLocationShareResult +import org.matrix.android.sdk.api.session.room.model.livelocation.LiveLocationShareAggregatedSummary +import org.matrix.android.sdk.api.util.Cancelable +import org.matrix.android.sdk.api.util.Optional +import org.matrix.android.sdk.api.util.toOptional +import org.matrix.android.sdk.internal.database.mapper.LiveLocationShareAggregatedSummaryMapper +import org.matrix.android.sdk.internal.database.model.livelocation.LiveLocationShareAggregatedSummaryEntity +import org.matrix.android.sdk.internal.database.model.livelocation.LiveLocationShareAggregatedSummaryEntityFields +import org.matrix.android.sdk.test.fakes.FakeMonarchy +import org.matrix.android.sdk.test.fakes.givenEqualTo +import org.matrix.android.sdk.test.fakes.givenIsNotEmpty +import org.matrix.android.sdk.test.fakes.givenIsNotNull + +private const val A_ROOM_ID = "room_id" +private const val AN_EVENT_ID = "event_id" +private const val A_LATITUDE = 1.4 +private const val A_LONGITUDE = 40.0 +private const val AN_UNCERTAINTY = 5.0 +private const val A_TIMEOUT = 15_000L + +@ExperimentalCoroutinesApi +internal class DefaultLocationSharingServiceTest { + + private val fakeMonarchy = FakeMonarchy() + private val sendStaticLocationTask = mockk() + private val sendLiveLocationTask = mockk() + private val startLiveLocationShareTask = mockk() + private val stopLiveLocationShareTask = mockk() + private val checkIfExistingActiveLiveTask = mockk() + private val fakeLiveLocationShareAggregatedSummaryMapper = mockk() + + private val defaultLocationSharingService = DefaultLocationSharingService( + roomId = A_ROOM_ID, + monarchy = fakeMonarchy.instance, + sendStaticLocationTask = sendStaticLocationTask, + sendLiveLocationTask = sendLiveLocationTask, + startLiveLocationShareTask = startLiveLocationShareTask, + stopLiveLocationShareTask = stopLiveLocationShareTask, + checkIfExistingActiveLiveTask = checkIfExistingActiveLiveTask, + liveLocationShareAggregatedSummaryMapper = fakeLiveLocationShareAggregatedSummaryMapper + ) + + @Before + fun setUp() { + mockkStatic("androidx.lifecycle.Transformations") + } + + @After + fun tearDown() { + unmockkAll() + } + + @Test + fun `static location can be sent`() = runTest { + val isUserLocation = true + val cancelable = mockk() + coEvery { sendStaticLocationTask.execute(any()) } returns cancelable + + val result = defaultLocationSharingService.sendStaticLocation( + latitude = A_LATITUDE, + longitude = A_LONGITUDE, + uncertainty = AN_UNCERTAINTY, + isUserLocation = isUserLocation + ) + + result shouldBeEqualTo cancelable + val expectedParams = SendStaticLocationTask.Params( + roomId = A_ROOM_ID, + latitude = A_LATITUDE, + longitude = A_LONGITUDE, + uncertainty = AN_UNCERTAINTY, + isUserLocation = isUserLocation, + ) + coVerify { sendStaticLocationTask.execute(expectedParams) } + } + + @Test + fun `live location can be sent`() = runTest { + val cancelable = mockk() + coEvery { sendLiveLocationTask.execute(any()) } returns cancelable + + val result = defaultLocationSharingService.sendLiveLocation( + beaconInfoEventId = AN_EVENT_ID, + latitude = A_LATITUDE, + longitude = A_LONGITUDE, + uncertainty = AN_UNCERTAINTY + ) + + result shouldBeEqualTo cancelable + val expectedParams = SendLiveLocationTask.Params( + roomId = A_ROOM_ID, + beaconInfoEventId = AN_EVENT_ID, + latitude = A_LATITUDE, + longitude = A_LONGITUDE, + uncertainty = AN_UNCERTAINTY + ) + coVerify { sendLiveLocationTask.execute(expectedParams) } + } + + @Test + fun `given existing active live can be stopped when starting a live then the current live is stopped and the new live is started`() = runTest { + coEvery { checkIfExistingActiveLiveTask.execute(any()) } returns true + coEvery { stopLiveLocationShareTask.execute(any()) } returns UpdateLiveLocationShareResult.Success("stopped-event-id") + coEvery { startLiveLocationShareTask.execute(any()) } returns UpdateLiveLocationShareResult.Success(AN_EVENT_ID) + + val result = defaultLocationSharingService.startLiveLocationShare(A_TIMEOUT) + + result shouldBeEqualTo UpdateLiveLocationShareResult.Success(AN_EVENT_ID) + val expectedCheckExistingParams = CheckIfExistingActiveLiveTask.Params( + roomId = A_ROOM_ID + ) + coVerify { checkIfExistingActiveLiveTask.execute(expectedCheckExistingParams) } + val expectedStopParams = StopLiveLocationShareTask.Params( + roomId = A_ROOM_ID + ) + coVerify { stopLiveLocationShareTask.execute(expectedStopParams) } + val expectedStartParams = StartLiveLocationShareTask.Params( + roomId = A_ROOM_ID, + timeoutMillis = A_TIMEOUT + ) + coVerify { startLiveLocationShareTask.execute(expectedStartParams) } + } + + @Test + fun `given existing active live cannot be stopped when starting a live then the result is failure`() = runTest { + coEvery { checkIfExistingActiveLiveTask.execute(any()) } returns true + val error = Throwable() + coEvery { stopLiveLocationShareTask.execute(any()) } returns UpdateLiveLocationShareResult.Failure(error) + + val result = defaultLocationSharingService.startLiveLocationShare(A_TIMEOUT) + + result shouldBeEqualTo UpdateLiveLocationShareResult.Failure(error) + val expectedCheckExistingParams = CheckIfExistingActiveLiveTask.Params( + roomId = A_ROOM_ID + ) + coVerify { checkIfExistingActiveLiveTask.execute(expectedCheckExistingParams) } + val expectedStopParams = StopLiveLocationShareTask.Params( + roomId = A_ROOM_ID + ) + coVerify { stopLiveLocationShareTask.execute(expectedStopParams) } + } + + @Test + fun `given no existing active live when starting a live then the new live is started`() = runTest { + coEvery { checkIfExistingActiveLiveTask.execute(any()) } returns false + coEvery { startLiveLocationShareTask.execute(any()) } returns UpdateLiveLocationShareResult.Success(AN_EVENT_ID) + + val result = defaultLocationSharingService.startLiveLocationShare(A_TIMEOUT) + + result shouldBeEqualTo UpdateLiveLocationShareResult.Success(AN_EVENT_ID) + val expectedCheckExistingParams = CheckIfExistingActiveLiveTask.Params( + roomId = A_ROOM_ID + ) + coVerify { checkIfExistingActiveLiveTask.execute(expectedCheckExistingParams) } + val expectedStartParams = StartLiveLocationShareTask.Params( + roomId = A_ROOM_ID, + timeoutMillis = A_TIMEOUT + ) + coVerify { startLiveLocationShareTask.execute(expectedStartParams) } + } + + @Test + fun `live location share can be stopped`() = runTest { + coEvery { stopLiveLocationShareTask.execute(any()) } returns UpdateLiveLocationShareResult.Success(AN_EVENT_ID) + + val result = defaultLocationSharingService.stopLiveLocationShare() + + result shouldBeEqualTo UpdateLiveLocationShareResult.Success(AN_EVENT_ID) + val expectedParams = StopLiveLocationShareTask.Params( + roomId = A_ROOM_ID + ) + coVerify { stopLiveLocationShareTask.execute(expectedParams) } + } + + @Test + fun `livedata of live summaries is correctly computed`() { + val entity = LiveLocationShareAggregatedSummaryEntity() + val summary = LiveLocationShareAggregatedSummary( + userId = "", + isActive = true, + endOfLiveTimestampMillis = 123, + lastLocationDataContent = null + ) + + fakeMonarchy.givenWhere() + .givenEqualTo(LiveLocationShareAggregatedSummaryEntityFields.ROOM_ID, A_ROOM_ID) + .givenEqualTo(LiveLocationShareAggregatedSummaryEntityFields.IS_ACTIVE, true) + .givenIsNotEmpty(LiveLocationShareAggregatedSummaryEntityFields.USER_ID) + .givenIsNotNull(LiveLocationShareAggregatedSummaryEntityFields.LAST_LOCATION_CONTENT) + fakeMonarchy.givenFindAllMappedWithChangesReturns( + realmEntities = listOf(entity), + mappedResult = listOf(summary), + fakeLiveLocationShareAggregatedSummaryMapper + ) + + val result = defaultLocationSharingService.getRunningLiveLocationShareSummaries().value + + result shouldBeEqualTo listOf(summary) + } + + @Test + fun `given an event id when getting livedata on corresponding live summary then it is correctly computed`() { + val entity = LiveLocationShareAggregatedSummaryEntity() + val summary = LiveLocationShareAggregatedSummary( + userId = "", + isActive = true, + endOfLiveTimestampMillis = 123, + lastLocationDataContent = null + ) + + fakeMonarchy.givenWhere() + .givenEqualTo(LiveLocationShareAggregatedSummaryEntityFields.ROOM_ID, A_ROOM_ID) + .givenEqualTo(LiveLocationShareAggregatedSummaryEntityFields.EVENT_ID, AN_EVENT_ID) + val liveData = fakeMonarchy.givenFindAllMappedWithChangesReturns( + realmEntities = listOf(entity), + mappedResult = listOf(summary), + fakeLiveLocationShareAggregatedSummaryMapper + ) + val mapper = slot, Optional>>() + every { + Transformations.map( + liveData, + capture(mapper) + ) + } answers { + val value = secondArg, Optional>>().apply(listOf(summary)) + MutableLiveData(value) + } + + val result = defaultLocationSharingService.getLiveLocationShareSummary(AN_EVENT_ID).value + + result shouldBeEqualTo summary.toOptional() + } +} diff --git a/matrix-sdk-android/src/test/java/org/matrix/android/sdk/internal/session/room/location/DefaultSendLiveLocationTaskTest.kt b/matrix-sdk-android/src/test/java/org/matrix/android/sdk/internal/session/room/location/DefaultSendLiveLocationTaskTest.kt new file mode 100644 index 0000000000..423c680054 --- /dev/null +++ b/matrix-sdk-android/src/test/java/org/matrix/android/sdk/internal/session/room/location/DefaultSendLiveLocationTaskTest.kt @@ -0,0 +1,79 @@ +/* + * 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.location + +import io.mockk.mockk +import io.mockk.unmockkAll +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.test.runTest +import org.amshove.kluent.shouldBeEqualTo +import org.junit.After +import org.junit.Test +import org.matrix.android.sdk.api.util.Cancelable +import org.matrix.android.sdk.test.fakes.FakeEventSenderProcessor +import org.matrix.android.sdk.test.fakes.FakeLocalEchoEventFactory + +private const val A_ROOM_ID = "room_id" +private const val AN_EVENT_ID = "event_id" +private const val A_LATITUDE = 1.4 +private const val A_LONGITUDE = 44.0 +private const val AN_UNCERTAINTY = 5.0 + +@ExperimentalCoroutinesApi +internal class DefaultSendLiveLocationTaskTest { + + private val fakeLocalEchoEventFactory = FakeLocalEchoEventFactory() + private val fakeEventSenderProcessor = FakeEventSenderProcessor() + + private val defaultSendLiveLocationTask = DefaultSendLiveLocationTask( + localEchoEventFactory = fakeLocalEchoEventFactory.instance, + eventSenderProcessor = fakeEventSenderProcessor + ) + + @After + fun tearDown() { + unmockkAll() + } + + @Test + fun `given parameters when calling the task then it is correctly executed`() = runTest { + val params = SendLiveLocationTask.Params( + roomId = A_ROOM_ID, + beaconInfoEventId = AN_EVENT_ID, + latitude = A_LATITUDE, + longitude = A_LONGITUDE, + uncertainty = AN_UNCERTAINTY + ) + val event = fakeLocalEchoEventFactory.givenCreateLiveLocationEvent( + withLocalEcho = true + ) + val cancelable = mockk() + fakeEventSenderProcessor.givenPostEventReturns(event, cancelable) + + val result = defaultSendLiveLocationTask.execute(params) + + result shouldBeEqualTo cancelable + fakeLocalEchoEventFactory.verifyCreateLiveLocationEvent( + roomId = params.roomId, + beaconInfoEventId = params.beaconInfoEventId, + latitude = params.latitude, + longitude = params.longitude, + uncertainty = params.uncertainty + ) + fakeLocalEchoEventFactory.verifyCreateLocalEcho(event) + } +} diff --git a/matrix-sdk-android/src/test/java/org/matrix/android/sdk/internal/session/room/location/DefaultSendStaticLocationTaskTest.kt b/matrix-sdk-android/src/test/java/org/matrix/android/sdk/internal/session/room/location/DefaultSendStaticLocationTaskTest.kt new file mode 100644 index 0000000000..cfde568b71 --- /dev/null +++ b/matrix-sdk-android/src/test/java/org/matrix/android/sdk/internal/session/room/location/DefaultSendStaticLocationTaskTest.kt @@ -0,0 +1,78 @@ +/* + * 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.location + +import io.mockk.mockk +import io.mockk.unmockkAll +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.test.runTest +import org.amshove.kluent.shouldBeEqualTo +import org.junit.After +import org.junit.Test +import org.matrix.android.sdk.api.util.Cancelable +import org.matrix.android.sdk.test.fakes.FakeEventSenderProcessor +import org.matrix.android.sdk.test.fakes.FakeLocalEchoEventFactory + +private const val A_ROOM_ID = "room_id" +private const val A_LATITUDE = 1.4 +private const val A_LONGITUDE = 44.0 +private const val AN_UNCERTAINTY = 5.0 + +@ExperimentalCoroutinesApi +internal class DefaultSendStaticLocationTaskTest { + + private val fakeLocalEchoEventFactory = FakeLocalEchoEventFactory() + private val fakeEventSenderProcessor = FakeEventSenderProcessor() + + private val defaultSendStaticLocationTask = DefaultSendStaticLocationTask( + localEchoEventFactory = fakeLocalEchoEventFactory.instance, + eventSenderProcessor = fakeEventSenderProcessor + ) + + @After + fun tearDown() { + unmockkAll() + } + + @Test + fun `given parameters when calling the task then it is correctly executed`() = runTest { + val params = SendStaticLocationTask.Params( + roomId = A_ROOM_ID, + latitude = A_LATITUDE, + longitude = A_LONGITUDE, + uncertainty = AN_UNCERTAINTY, + isUserLocation = true + ) + val event = fakeLocalEchoEventFactory.givenCreateStaticLocationEvent( + withLocalEcho = true + ) + val cancelable = mockk() + fakeEventSenderProcessor.givenPostEventReturns(event, cancelable) + + val result = defaultSendStaticLocationTask.execute(params) + + result shouldBeEqualTo cancelable + fakeLocalEchoEventFactory.verifyCreateStaticLocationEvent( + roomId = params.roomId, + latitude = params.latitude, + longitude = params.longitude, + uncertainty = params.uncertainty, + isUserLocation = params.isUserLocation + ) + fakeLocalEchoEventFactory.verifyCreateLocalEcho(event) + } +} diff --git a/matrix-sdk-android/src/test/java/org/matrix/android/sdk/internal/session/room/location/DefaultStartLiveLocationShareTaskTest.kt b/matrix-sdk-android/src/test/java/org/matrix/android/sdk/internal/session/room/location/DefaultStartLiveLocationShareTaskTest.kt new file mode 100644 index 0000000000..909ba5d048 --- /dev/null +++ b/matrix-sdk-android/src/test/java/org/matrix/android/sdk/internal/session/room/location/DefaultStartLiveLocationShareTaskTest.kt @@ -0,0 +1,114 @@ +/* + * 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.location + +import io.mockk.unmockkAll +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.test.runTest +import org.amshove.kluent.shouldBeEqualTo +import org.amshove.kluent.shouldBeInstanceOf +import org.junit.After +import org.junit.Test +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.room.location.UpdateLiveLocationShareResult +import org.matrix.android.sdk.api.session.room.model.message.MessageBeaconInfoContent +import org.matrix.android.sdk.internal.session.room.state.SendStateTask +import org.matrix.android.sdk.test.fakes.FakeClock +import org.matrix.android.sdk.test.fakes.FakeSendStateTask + +private const val A_USER_ID = "user-id" +private const val A_ROOM_ID = "room-id" +private const val AN_EVENT_ID = "event-id" +private const val A_TIMEOUT = 15_000L +private const val AN_EPOCH = 1655210176L + +@ExperimentalCoroutinesApi +internal class DefaultStartLiveLocationShareTaskTest { + + private val fakeClock = FakeClock() + private val fakeSendStateTask = FakeSendStateTask() + + private val defaultStartLiveLocationShareTask = DefaultStartLiveLocationShareTask( + userId = A_USER_ID, + clock = fakeClock, + sendStateTask = fakeSendStateTask + ) + + @After + fun tearDown() { + unmockkAll() + } + + @Test + fun `given parameters and no error when calling the task then result is success`() = runTest { + val params = StartLiveLocationShareTask.Params( + roomId = A_ROOM_ID, + timeoutMillis = A_TIMEOUT + ) + fakeClock.givenEpoch(AN_EPOCH) + fakeSendStateTask.givenExecuteRetryReturns(AN_EVENT_ID) + + val result = defaultStartLiveLocationShareTask.execute(params) + + result shouldBeEqualTo UpdateLiveLocationShareResult.Success(AN_EVENT_ID) + val expectedBeaconContent = MessageBeaconInfoContent( + timeout = params.timeoutMillis, + isLive = true, + unstableTimestampMillis = AN_EPOCH + ).toContent() + val expectedParams = SendStateTask.Params( + roomId = params.roomId, + stateKey = A_USER_ID, + eventType = EventType.STATE_ROOM_BEACON_INFO.first(), + body = expectedBeaconContent + ) + fakeSendStateTask.verifyExecuteRetry( + params = expectedParams, + remainingRetry = 3 + ) + } + + @Test + fun `given parameters and an empty returned event id when calling the task then result is failure`() = runTest { + val params = StartLiveLocationShareTask.Params( + roomId = A_ROOM_ID, + timeoutMillis = A_TIMEOUT + ) + fakeClock.givenEpoch(AN_EPOCH) + fakeSendStateTask.givenExecuteRetryReturns("") + + val result = defaultStartLiveLocationShareTask.execute(params) + + result shouldBeInstanceOf UpdateLiveLocationShareResult.Failure::class + } + + @Test + fun `given parameters and error during event sending when calling the task then result is failure`() = runTest { + val params = StartLiveLocationShareTask.Params( + roomId = A_ROOM_ID, + timeoutMillis = A_TIMEOUT + ) + fakeClock.givenEpoch(AN_EPOCH) + val error = Throwable() + fakeSendStateTask.givenExecuteRetryThrows(error) + + val result = defaultStartLiveLocationShareTask.execute(params) + + result shouldBeEqualTo UpdateLiveLocationShareResult.Failure(error) + } +} diff --git a/matrix-sdk-android/src/test/java/org/matrix/android/sdk/internal/session/room/location/DefaultStopLiveLocationShareTaskTest.kt b/matrix-sdk-android/src/test/java/org/matrix/android/sdk/internal/session/room/location/DefaultStopLiveLocationShareTaskTest.kt new file mode 100644 index 0000000000..1abf179ccf --- /dev/null +++ b/matrix-sdk-android/src/test/java/org/matrix/android/sdk/internal/session/room/location/DefaultStopLiveLocationShareTaskTest.kt @@ -0,0 +1,167 @@ +/* + * 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.location + +import io.mockk.unmockkAll +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.test.runTest +import org.amshove.kluent.shouldBeEqualTo +import org.amshove.kluent.shouldBeInstanceOf +import org.junit.After +import org.junit.Test +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.room.location.UpdateLiveLocationShareResult +import org.matrix.android.sdk.api.session.room.model.message.MessageBeaconInfoContent +import org.matrix.android.sdk.internal.session.room.state.SendStateTask +import org.matrix.android.sdk.test.fakes.FakeGetActiveBeaconInfoForUserTask +import org.matrix.android.sdk.test.fakes.FakeSendStateTask + +private const val A_USER_ID = "user-id" +private const val A_ROOM_ID = "room-id" +private const val AN_EVENT_ID = "event-id" +private const val A_TIMEOUT = 15_000L +private const val AN_EPOCH = 1655210176L + +@ExperimentalCoroutinesApi +class DefaultStopLiveLocationShareTaskTest { + + private val fakeSendStateTask = FakeSendStateTask() + private val fakeGetActiveBeaconInfoForUserTask = FakeGetActiveBeaconInfoForUserTask() + + private val defaultStopLiveLocationShareTask = DefaultStopLiveLocationShareTask( + sendStateTask = fakeSendStateTask, + getActiveBeaconInfoForUserTask = fakeGetActiveBeaconInfoForUserTask + ) + + @After + fun tearDown() { + unmockkAll() + } + + @Test + fun `given parameters and no error when calling the task then result is success`() = runTest { + val params = StopLiveLocationShareTask.Params(roomId = A_ROOM_ID) + val currentStateEvent = Event( + stateKey = A_USER_ID, + content = MessageBeaconInfoContent( + timeout = A_TIMEOUT, + isLive = true, + unstableTimestampMillis = AN_EPOCH + ).toContent() + ) + fakeGetActiveBeaconInfoForUserTask.givenExecuteReturns(currentStateEvent) + fakeSendStateTask.givenExecuteRetryReturns(AN_EVENT_ID) + + val result = defaultStopLiveLocationShareTask.execute(params) + + result shouldBeEqualTo UpdateLiveLocationShareResult.Success(AN_EVENT_ID) + val expectedBeaconContent = MessageBeaconInfoContent( + timeout = A_TIMEOUT, + isLive = false, + unstableTimestampMillis = AN_EPOCH + ).toContent() + val expectedSendParams = SendStateTask.Params( + roomId = params.roomId, + stateKey = A_USER_ID, + eventType = EventType.STATE_ROOM_BEACON_INFO.first(), + body = expectedBeaconContent + ) + fakeSendStateTask.verifyExecuteRetry( + params = expectedSendParams, + remainingRetry = 3 + ) + val expectedGetBeaconParams = GetActiveBeaconInfoForUserTask.Params( + roomId = params.roomId + ) + fakeGetActiveBeaconInfoForUserTask.verifyExecute( + expectedGetBeaconParams + ) + } + + @Test + fun `given parameters and an incorrect current state event when calling the task then result is failure`() = runTest { + val incorrectCurrentStateEvents = listOf( + // no event + null, + // no stateKey + Event( + stateKey = null, + content = MessageBeaconInfoContent( + timeout = A_TIMEOUT, + isLive = true, + unstableTimestampMillis = AN_EPOCH + ).toContent() + ), + // null content + Event( + stateKey = A_USER_ID, + content = null + ) + ) + + incorrectCurrentStateEvents.forEach { currentStateEvent -> + fakeGetActiveBeaconInfoForUserTask.givenExecuteReturns(currentStateEvent) + fakeSendStateTask.givenExecuteRetryReturns(AN_EVENT_ID) + val params = StopLiveLocationShareTask.Params(roomId = A_ROOM_ID) + + val result = defaultStopLiveLocationShareTask.execute(params) + + result shouldBeInstanceOf UpdateLiveLocationShareResult.Failure::class + } + } + + @Test + fun `given parameters and an empty returned event id when calling the task then result is failure`() = runTest { + val params = StopLiveLocationShareTask.Params(roomId = A_ROOM_ID) + val currentStateEvent = Event( + stateKey = A_USER_ID, + content = MessageBeaconInfoContent( + timeout = A_TIMEOUT, + isLive = true, + unstableTimestampMillis = AN_EPOCH + ).toContent() + ) + fakeGetActiveBeaconInfoForUserTask.givenExecuteReturns(currentStateEvent) + fakeSendStateTask.givenExecuteRetryReturns("") + + val result = defaultStopLiveLocationShareTask.execute(params) + + result shouldBeInstanceOf UpdateLiveLocationShareResult.Failure::class + } + + @Test + fun `given parameters and error during event sending when calling the task then result is failure`() = runTest { + val params = StopLiveLocationShareTask.Params(roomId = A_ROOM_ID) + val currentStateEvent = Event( + stateKey = A_USER_ID, + content = MessageBeaconInfoContent( + timeout = A_TIMEOUT, + isLive = true, + unstableTimestampMillis = AN_EPOCH + ).toContent() + ) + fakeGetActiveBeaconInfoForUserTask.givenExecuteReturns(currentStateEvent) + val error = Throwable() + fakeSendStateTask.givenExecuteRetryThrows(error) + + val result = defaultStopLiveLocationShareTask.execute(params) + + result shouldBeEqualTo UpdateLiveLocationShareResult.Failure(error) + } +} diff --git a/matrix-sdk-android/src/test/java/org/matrix/android/sdk/test/fakes/FakeClock.kt b/matrix-sdk-android/src/test/java/org/matrix/android/sdk/test/fakes/FakeClock.kt new file mode 100644 index 0000000000..febf94f4cf --- /dev/null +++ b/matrix-sdk-android/src/test/java/org/matrix/android/sdk/test/fakes/FakeClock.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.test.fakes + +import io.mockk.every +import io.mockk.mockk +import org.matrix.android.sdk.internal.util.time.Clock + +internal class FakeClock : Clock by mockk() { + fun givenEpoch(epoch: Long) { + every { epochMillis() } returns epoch + } +} diff --git a/matrix-sdk-android/src/test/java/org/matrix/android/sdk/test/fakes/FakeEventSenderProcessor.kt b/matrix-sdk-android/src/test/java/org/matrix/android/sdk/test/fakes/FakeEventSenderProcessor.kt new file mode 100644 index 0000000000..fbdcf5bfd7 --- /dev/null +++ b/matrix-sdk-android/src/test/java/org/matrix/android/sdk/test/fakes/FakeEventSenderProcessor.kt @@ -0,0 +1,30 @@ +/* + * 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.test.fakes + +import io.mockk.every +import io.mockk.mockk +import org.matrix.android.sdk.api.session.events.model.Event +import org.matrix.android.sdk.api.util.Cancelable +import org.matrix.android.sdk.internal.session.room.send.queue.EventSenderProcessor + +internal class FakeEventSenderProcessor : EventSenderProcessor by mockk() { + + fun givenPostEventReturns(event: Event, cancelable: Cancelable) { + every { postEvent(event) } returns cancelable + } +} diff --git a/matrix-sdk-android/src/test/java/org/matrix/android/sdk/test/fakes/FakeGetActiveBeaconInfoForUserTask.kt b/matrix-sdk-android/src/test/java/org/matrix/android/sdk/test/fakes/FakeGetActiveBeaconInfoForUserTask.kt new file mode 100644 index 0000000000..dc4a48908a --- /dev/null +++ b/matrix-sdk-android/src/test/java/org/matrix/android/sdk/test/fakes/FakeGetActiveBeaconInfoForUserTask.kt @@ -0,0 +1,34 @@ +/* + * Copyright (c) 2022 The Matrix.org Foundation C.I.C. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.matrix.android.sdk.test.fakes + +import io.mockk.coEvery +import io.mockk.coVerify +import io.mockk.mockk +import org.matrix.android.sdk.api.session.events.model.Event +import org.matrix.android.sdk.internal.session.room.location.GetActiveBeaconInfoForUserTask + +internal class FakeGetActiveBeaconInfoForUserTask : GetActiveBeaconInfoForUserTask by mockk() { + + fun givenExecuteReturns(event: Event?) { + coEvery { execute(any()) } returns event + } + + fun verifyExecute(params: GetActiveBeaconInfoForUserTask.Params) { + coVerify { execute(params) } + } +} diff --git a/matrix-sdk-android/src/test/java/org/matrix/android/sdk/test/fakes/FakeLocalEchoEventFactory.kt b/matrix-sdk-android/src/test/java/org/matrix/android/sdk/test/fakes/FakeLocalEchoEventFactory.kt new file mode 100644 index 0000000000..50ec85f14a --- /dev/null +++ b/matrix-sdk-android/src/test/java/org/matrix/android/sdk/test/fakes/FakeLocalEchoEventFactory.kt @@ -0,0 +1,106 @@ +/* + * 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.test.fakes + +import io.mockk.every +import io.mockk.just +import io.mockk.mockk +import io.mockk.runs +import io.mockk.verify +import org.matrix.android.sdk.api.session.events.model.Event +import org.matrix.android.sdk.internal.session.room.send.LocalEchoEventFactory + +internal class FakeLocalEchoEventFactory { + + val instance = mockk() + + fun givenCreateStaticLocationEvent(withLocalEcho: Boolean): Event { + val event = Event() + every { + instance.createStaticLocationEvent( + roomId = any(), + latitude = any(), + longitude = any(), + uncertainty = any(), + isUserLocation = any() + ) + } returns event + + if (withLocalEcho) { + every { instance.createLocalEcho(event) } just runs + } + return event + } + + fun givenCreateLiveLocationEvent(withLocalEcho: Boolean): Event { + val event = Event() + every { + instance.createLiveLocationEvent( + beaconInfoEventId = any(), + roomId = any(), + latitude = any(), + longitude = any(), + uncertainty = any() + ) + } returns event + + if (withLocalEcho) { + every { instance.createLocalEcho(event) } just runs + } + return event + } + + fun verifyCreateStaticLocationEvent( + roomId: String, + latitude: Double, + longitude: Double, + uncertainty: Double?, + isUserLocation: Boolean + ) { + verify { + instance.createStaticLocationEvent( + roomId = roomId, + latitude = latitude, + longitude = longitude, + uncertainty = uncertainty, + isUserLocation = isUserLocation + ) + } + } + + fun verifyCreateLiveLocationEvent( + roomId: String, + beaconInfoEventId: String, + latitude: Double, + longitude: Double, + uncertainty: Double? + ) { + verify { + instance.createLiveLocationEvent( + roomId = roomId, + beaconInfoEventId = beaconInfoEventId, + latitude = latitude, + longitude = longitude, + uncertainty = uncertainty + ) + } + } + + fun verifyCreateLocalEcho(event: Event) { + verify { instance.createLocalEcho(event) } + } +} diff --git a/matrix-sdk-android/src/test/java/org/matrix/android/sdk/test/fakes/FakeMonarchy.kt b/matrix-sdk-android/src/test/java/org/matrix/android/sdk/test/fakes/FakeMonarchy.kt index 0a22ef8996..d77084fe3b 100644 --- a/matrix-sdk-android/src/test/java/org/matrix/android/sdk/test/fakes/FakeMonarchy.kt +++ b/matrix-sdk-android/src/test/java/org/matrix/android/sdk/test/fakes/FakeMonarchy.kt @@ -16,40 +16,65 @@ package org.matrix.android.sdk.test.fakes +import androidx.lifecycle.LiveData +import androidx.lifecycle.MutableLiveData import com.zhuinden.monarchy.Monarchy import io.mockk.MockKVerificationScope import io.mockk.coEvery import io.mockk.every import io.mockk.mockk import io.mockk.mockkStatic -import io.mockk.verify +import io.mockk.slot import io.realm.Realm import io.realm.RealmModel import io.realm.RealmQuery -import io.realm.kotlin.where import org.matrix.android.sdk.internal.util.awaitTransaction internal class FakeMonarchy { val instance = mockk() - private val realm = mockk(relaxed = true) + private val fakeRealm = FakeRealm() init { mockkStatic("org.matrix.android.sdk.internal.util.MonarchyKt") coEvery { instance.awaitTransaction(any Any>()) } coAnswers { - secondArg Any>().invoke(realm) + secondArg Any>().invoke(fakeRealm.instance) } } - inline fun givenWhereReturns(result: T?) { - val queryResult = mockk>(relaxed = true) - every { queryResult.findFirst() } returns result - every { realm.where() } returns queryResult + inline fun givenWhere(): RealmQuery { + return fakeRealm.givenWhere() + } + + inline fun givenWhereReturns(result: T?): RealmQuery { + return fakeRealm.givenWhere() + .givenFindFirst(result) } inline fun verifyInsertOrUpdate(crossinline verification: MockKVerificationScope.() -> T) { - verify { realm.insertOrUpdate(verification()) } + fakeRealm.verifyInsertOrUpdate(verification) + } + + inline fun givenFindAllMappedWithChangesReturns( + realmEntities: List, + mappedResult: List, + mapper: Monarchy.Mapper + ): LiveData> { + every { mapper.map(any()) } returns mockk() + val monarchyQuery = slot>() + val monarchyMapper = slot>() + val result = MutableLiveData(mappedResult) + every { + instance.findAllMappedWithChanges(capture(monarchyQuery), capture(monarchyMapper)) + } answers { + monarchyQuery.captured.createQuery(fakeRealm.instance) + realmEntities.forEach { + monarchyMapper.captured.map(it) + } + result + } + return result } } diff --git a/matrix-sdk-android/src/test/java/org/matrix/android/sdk/test/fakes/FakeRealm.kt b/matrix-sdk-android/src/test/java/org/matrix/android/sdk/test/fakes/FakeRealm.kt index c07f8e1873..0ebff87278 100644 --- a/matrix-sdk-android/src/test/java/org/matrix/android/sdk/test/fakes/FakeRealm.kt +++ b/matrix-sdk-android/src/test/java/org/matrix/android/sdk/test/fakes/FakeRealm.kt @@ -16,21 +16,84 @@ package org.matrix.android.sdk.test.fakes +import io.mockk.MockKVerificationScope import io.mockk.every import io.mockk.mockk +import io.mockk.verify import io.realm.Realm import io.realm.RealmModel import io.realm.RealmQuery +import io.realm.RealmResults import io.realm.kotlin.where internal class FakeRealm { val instance = mockk(relaxed = true) - inline fun givenWhereReturns(result: T?): RealmQuery { - val queryResult = mockk>() - every { queryResult.findFirst() } returns result - every { instance.where() } returns queryResult - return queryResult + inline fun givenWhere(): RealmQuery { + val query = mockk>() + every { instance.where() } returns query + return query + } + + inline fun verifyInsertOrUpdate(crossinline verification: MockKVerificationScope.() -> T) { + verify { instance.insertOrUpdate(verification()) } } } + +inline fun RealmQuery.givenFindFirst( + result: T? +): RealmQuery { + every { findFirst() } returns result + return this +} + +inline fun RealmQuery.givenFindAll( + result: List +): RealmQuery { + val realmResults = mockk>() + result.forEachIndexed { index, t -> + every { realmResults[index] } returns t + } + every { realmResults.size } returns result.size + every { findAll() } returns realmResults + return this +} + +inline fun RealmQuery.givenEqualTo( + fieldName: String, + value: String +): RealmQuery { + every { equalTo(fieldName, value) } returns this + return this +} + +inline fun RealmQuery.givenEqualTo( + fieldName: String, + value: Boolean +): RealmQuery { + every { equalTo(fieldName, value) } returns this + return this +} + +inline fun RealmQuery.givenNotEqualTo( + fieldName: String, + value: String +): RealmQuery { + every { notEqualTo(fieldName, value) } returns this + return this +} + +inline fun RealmQuery.givenIsNotEmpty( + fieldName: String +): RealmQuery { + every { isNotEmpty(fieldName) } returns this + return this +} + +inline fun RealmQuery.givenIsNotNull( + fieldName: String +): RealmQuery { + every { isNotNull(fieldName) } returns this + return this +} diff --git a/matrix-sdk-android/src/test/java/org/matrix/android/sdk/test/fakes/FakeRequestExecutor.kt b/matrix-sdk-android/src/test/java/org/matrix/android/sdk/test/fakes/FakeRequestExecutor.kt index 2f332a89a8..ad20abf63c 100644 --- a/matrix-sdk-android/src/test/java/org/matrix/android/sdk/test/fakes/FakeRequestExecutor.kt +++ b/matrix-sdk-android/src/test/java/org/matrix/android/sdk/test/fakes/FakeRequestExecutor.kt @@ -21,11 +21,13 @@ import org.matrix.android.sdk.internal.network.RequestExecutor internal class FakeRequestExecutor : RequestExecutor { - override suspend fun executeRequest(globalErrorReceiver: GlobalErrorReceiver?, - canRetry: Boolean, - maxDelayBeforeRetry: Long, - maxRetriesCount: Int, - requestBlock: suspend () -> DATA): DATA { + override suspend fun executeRequest( + globalErrorReceiver: GlobalErrorReceiver?, + canRetry: Boolean, + maxDelayBeforeRetry: Long, + maxRetriesCount: Int, + requestBlock: suspend () -> DATA + ): DATA { return requestBlock() } } diff --git a/matrix-sdk-android/src/test/java/org/matrix/android/sdk/test/fakes/FakeSendStateTask.kt b/matrix-sdk-android/src/test/java/org/matrix/android/sdk/test/fakes/FakeSendStateTask.kt new file mode 100644 index 0000000000..08a25be93e --- /dev/null +++ b/matrix-sdk-android/src/test/java/org/matrix/android/sdk/test/fakes/FakeSendStateTask.kt @@ -0,0 +1,37 @@ +/* + * 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.test.fakes + +import io.mockk.coEvery +import io.mockk.coVerify +import io.mockk.mockk +import org.matrix.android.sdk.internal.session.room.state.SendStateTask + +internal class FakeSendStateTask : SendStateTask by mockk() { + + fun givenExecuteRetryReturns(eventId: String) { + coEvery { executeRetry(any(), any()) } returns eventId + } + + fun givenExecuteRetryThrows(error: Throwable) { + coEvery { executeRetry(any(), any()) } throws error + } + + fun verifyExecuteRetry(params: SendStateTask.Params, remainingRetry: Int) { + coVerify { executeRetry(params, remainingRetry) } + } +} diff --git a/matrix-sdk-android/src/test/java/org/matrix/android/sdk/test/fakes/FakeStateEventDataSource.kt b/matrix-sdk-android/src/test/java/org/matrix/android/sdk/test/fakes/FakeStateEventDataSource.kt new file mode 100644 index 0000000000..ca03316fa7 --- /dev/null +++ b/matrix-sdk-android/src/test/java/org/matrix/android/sdk/test/fakes/FakeStateEventDataSource.kt @@ -0,0 +1,49 @@ +/* + * 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.test.fakes + +import io.mockk.every +import io.mockk.mockk +import io.mockk.verify +import org.matrix.android.sdk.api.query.QueryStringValue +import org.matrix.android.sdk.api.session.events.model.Event +import org.matrix.android.sdk.internal.session.room.state.StateEventDataSource + +internal class FakeStateEventDataSource { + + val instance: StateEventDataSource = mockk() + + fun givenGetStateEventReturns(event: Event?) { + every { + instance.getStateEvent( + roomId = any(), + eventType = any(), + stateKey = any() + ) + } returns event + } + + fun verifyGetStateEvent(roomId: String, eventType: String, stateKey: String) { + verify { + instance.getStateEvent( + roomId = roomId, + eventType = eventType, + stateKey = QueryStringValue.Equals(stateKey) + ) + } + } +} diff --git a/matrix-sdk-android/src/test/java/org/matrix/android/sdk/test/fakes/FakeWorkManager.kt b/matrix-sdk-android/src/test/java/org/matrix/android/sdk/test/fakes/FakeWorkManager.kt new file mode 100644 index 0000000000..b29d015a43 --- /dev/null +++ b/matrix-sdk-android/src/test/java/org/matrix/android/sdk/test/fakes/FakeWorkManager.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.test.fakes + +import androidx.work.ExistingWorkPolicy +import androidx.work.OneTimeWorkRequest +import androidx.work.WorkManager +import io.mockk.every +import io.mockk.mockk +import io.mockk.verify + +class FakeWorkManager { + + val instance = mockk() + + fun expectEnqueueUniqueWork() { + every { instance.enqueueUniqueWork(any(), any(), any()) } returns mockk() + } + + fun verifyEnqueueUniqueWork(workName: String, policy: ExistingWorkPolicy) { + verify { instance.enqueueUniqueWork(workName, policy, any()) } + } + + fun expectCancelUniqueWork() { + every { instance.cancelUniqueWork(any()) } returns mockk() + } + + fun verifyCancelUniqueWork(workName: String) { + verify { instance.cancelUniqueWork(workName) } + } +} diff --git a/matrix-sdk-android/src/test/java/org/matrix/android/sdk/test/fakes/FakeWorkManagerProvider.kt b/matrix-sdk-android/src/test/java/org/matrix/android/sdk/test/fakes/FakeWorkManagerProvider.kt new file mode 100644 index 0000000000..51ff24c01d --- /dev/null +++ b/matrix-sdk-android/src/test/java/org/matrix/android/sdk/test/fakes/FakeWorkManagerProvider.kt @@ -0,0 +1,30 @@ +/* + * 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.test.fakes + +import io.mockk.every +import io.mockk.mockk +import org.matrix.android.sdk.internal.di.WorkManagerProvider + +internal class FakeWorkManagerProvider( + val fakeWorkManager: FakeWorkManager = FakeWorkManager(), +) { + + val instance = mockk().also { + every { it.workManager } returns fakeWorkManager.instance + } +} diff --git a/matrix-sdk-android/src/test/java/org/matrix/android/sdk/util/DefaultBuildVersionSdkIntProviderTests.kt b/matrix-sdk-android/src/test/java/org/matrix/android/sdk/util/DefaultBuildVersionSdkIntProviderTests.kt new file mode 100644 index 0000000000..c118cf07a1 --- /dev/null +++ b/matrix-sdk-android/src/test/java/org/matrix/android/sdk/util/DefaultBuildVersionSdkIntProviderTests.kt @@ -0,0 +1,31 @@ +/* + * 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.util + +import android.os.Build +import org.amshove.kluent.shouldBeEqualTo +import org.junit.Test +import org.matrix.android.sdk.api.util.DefaultBuildVersionSdkIntProvider + +class DefaultBuildVersionSdkIntProviderTests { + + @Test + fun getReturnsCurrentVersionFromBuild_Version_SDK_INT() { + val provider = DefaultBuildVersionSdkIntProvider() + provider.get() shouldBeEqualTo Build.VERSION.SDK_INT + } +} diff --git a/settings.gradle b/settings.gradle index 782d2caf4a..0f537ed48a 100644 --- a/settings.gradle +++ b/settings.gradle @@ -8,3 +8,4 @@ include ':library:attachment-viewer' include ':library:diff-match-patch' include ':library:multipicker' include ':matrix-sdk-android-flow' +include ':library:opusencoder' diff --git a/tools/dependencycheck/suppressions.xml b/tools/dependencycheck/suppressions.xml index 758b1a87f3..932bcdc08d 100644 --- a/tools/dependencycheck/suppressions.xml +++ b/tools/dependencycheck/suppressions.xml @@ -14,4 +14,11 @@ ^pkg:maven/com\.pinterest\.ktlint/ktlint\-reporter\-checkstyle@.*$ CVE-2019-9658 + + + ^pkg:maven/io\.github\.detekt\.sarif4k/sarif4k@.*$ + cpe:/a:detekt:detekt + diff --git a/vector-config/build.gradle b/vector-config/build.gradle index 4156e74274..658452bbdd 100644 --- a/vector-config/build.gradle +++ b/vector-config/build.gradle @@ -1,6 +1,6 @@ plugins { id 'com.android.library' - id 'org.jetbrains.kotlin.android' + id 'kotlin-android' } android { @@ -14,6 +14,7 @@ android { sourceCompatibility versions.sourceCompat targetCompatibility versions.targetCompat } + kotlinOptions { jvmTarget = "11" } diff --git a/vector-config/src/main/java/im/vector/app/config/Config.kt b/vector-config/src/main/java/im/vector/app/config/Config.kt new file mode 100644 index 0000000000..7577e6dba5 --- /dev/null +++ b/vector-config/src/main/java/im/vector/app/config/Config.kt @@ -0,0 +1,39 @@ +/* + * 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.config + +/** + * Set of flags to configure the application. + */ +object Config { + /** + * Flag to allow external UnifiedPush distributors to be chosen by the user. + * + * Set to true to allow any available external UnifiedPush distributor to be chosen by the user. + * - For Gplay variant it means that FCM will be used by default, but user can choose another UnifiedPush distributor; + * - For F-Droid variant, it means that background polling will be used by default, but user can choose another UnifiedPush distributor. + * + * Set to false to prevent usage of external UnifiedPush distributors. + * - For Gplay variant it means that only FCM will be used; + * - For F-Droid variant, it means that only background polling will be available to the user. + * + * *Note*: When the app is already installed on users' phone: + * - Changing the value from `false` to `true` will let the user be able to select an external UnifiedPush distributor; + * - Changing the value from `true` to `false` will force the app to return to the background sync / Firebase Push. + */ + const val ALLOW_EXTERNAL_UNIFIED_PUSH_DISTRIBUTORS = true +} diff --git a/vector-config/src/main/res/values/config.xml b/vector-config/src/main/res/values/config.xml index 78b92cbfa4..cae094f454 100755 --- a/vector-config/src/main/res/values/config.xml +++ b/vector-config/src/main/res/values/config.xml @@ -17,7 +17,11 @@ --> + https://matrix.org/_matrix/push/v1/notify + + + https://matrix.gateway.unifiedpush.org/_matrix/push/v1/notify im.vector.app.android diff --git a/vector/build.gradle b/vector/build.gradle index 4ae6527e0b..f9ca36f1b5 100644 --- a/vector/build.gradle +++ b/vector/build.gradle @@ -9,6 +9,10 @@ apply plugin: 'com.likethesalad.stem' apply plugin: 'dagger.hilt.android.plugin' apply plugin: 'kotlinx-knit' +if (project.hasProperty("coverage")) { + apply plugin: 'jacoco' +} + kapt { correctErrorTypes = true } @@ -31,7 +35,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 = 18 +ext.versionPatch = 26 static def getGitTimestamp() { def cmd = 'git show -s --format=%ct' @@ -119,8 +123,6 @@ ext.abiVersionCodes = ["armeabi-v7a": 1, "arm64-v8a": 2, "x86": 3, "x86_64": 4]. def buildNumber = System.env.BUILDKITE_BUILD_NUMBER as Integer ?: 0 android { - - // Due to a bug introduced in Android gradle plugin 3.6.0, we have to specify the ndk version to use // Ref: https://issuetracker.google.com/issues/144111441 ndkVersion "21.3.6528147" @@ -145,7 +147,7 @@ android { versionName "${versionMajor}.${versionMinor}.${versionPatch}-sonar" // Generate a random app task affinity - manifestPlaceholders = [appTaskAffinitySuffix:"H_${gitRevision()}"] + manifestPlaceholders = [appTaskAffinitySuffix: "H_${gitRevision()}"] buildConfigField "String", "GIT_REVISION", "\"${gitRevision()}\"" buildConfigField "String", "GIT_REVISION_DATE", "\"${gitRevisionDate()}\"" @@ -244,6 +246,10 @@ android { buildConfigField "boolean", "ENABLE_STRICT_MODE_LOGS", "false" signingConfig signingConfigs.debug + + if (project.hasProperty("coverage")) { + testCoverageEnabled = coverage.enableTestCoverage + } } release { @@ -336,6 +342,8 @@ android { } dependencies { + implementation project(':library:opusencoder') + implementation project(":vector-config") implementation project(":matrix-sdk-android") implementation project(":matrix-sdk-android-flow") @@ -358,6 +366,7 @@ dependencies { implementation libs.androidx.core implementation "androidx.media:media:1.6.0" implementation "androidx.transition:transition:1.4.1" + implementation libs.androidx.biometric implementation "org.threeten:threetenbp:1.4.0:no-tzdb" implementation "com.gabrielittner.threetenbp:lazythreetenbp:0.10.0" @@ -370,7 +379,6 @@ dependencies { implementation libs.androidx.lifecycleProcess implementation libs.androidx.lifecycleRuntimeKtx - implementation libs.androidx.datastore implementation libs.androidx.datastorepreferences @@ -381,12 +389,11 @@ dependencies { implementation 'com.facebook.stetho:stetho:1.6.0' // Phone number https://github.com/google/libphonenumber - implementation 'com.googlecode.libphonenumber:libphonenumber:8.12.48' + implementation 'com.googlecode.libphonenumber:libphonenumber:8.12.50' // FlowBinding implementation libs.github.flowBinding implementation libs.github.flowBindingAppcompat - implementation libs.github.flowBindingMaterial implementation libs.airbnb.epoxy implementation libs.airbnb.epoxyGlide @@ -419,7 +426,6 @@ dependencies { implementation 'com.google.android.flexbox:flexbox:3.0.0' implementation libs.androidx.autoFill implementation 'jp.wasabeef:glide-transformations:4.3.0' - implementation 'com.github.vector-im:PFLockScreen-Android:1.0.0-beta12' implementation 'com.github.hyuwah:DraggableView:1.0.0' // Custom Tab @@ -428,9 +434,6 @@ dependencies { // Passphrase strength helper implementation 'com.nulab-inc:zxcvbn:1.7.0' - // To convert voice message on old platforms. Always keep the LTS suffix! - implementation 'com.arthenica:ffmpeg-kit-audio:4.5.1.LTS' - // Alerter implementation 'com.github.tapadoo:alerter:7.2.4' @@ -463,8 +466,10 @@ dependencies { // Analytics implementation 'com.posthog.android:posthog:1.1.2' - // gplay flavor only - gplayImplementation('com.google.firebase:firebase-messaging:23.0.0') { + // UnifiedPush + implementation 'com.github.UnifiedPush:android-connector:2.0.0' + // UnifiedPush gplay flavor only + gplayImplementation('com.github.UnifiedPush:android-embedded_fcm_distributor:2.1.1') { exclude group: 'com.google.firebase', module: 'firebase-core' exclude group: 'com.google.firebase', module: 'firebase-analytics' exclude group: 'com.google.firebase', module: 'firebase-measurement-connector' @@ -507,9 +512,14 @@ dependencies { implementation 'commons-codec:commons-codec:1.15' // MapTiler - implementation 'org.maplibre.gl:android-sdk:9.5.2' - implementation 'org.maplibre.gl:android-plugin-annotation-v9:1.0.0' - + fdroidImplementation(libs.maplibre.androidSdk) { + exclude group: 'com.google.android.gms', module: 'play-services-location' + } + fdroidImplementation(libs.maplibre.pluginAnnotation) { + exclude group: 'com.google.android.gms', module: 'play-services-location' + } + gplayImplementation libs.maplibre.androidSdk + gplayImplementation libs.maplibre.pluginAnnotation // TESTS testImplementation libs.tests.junit @@ -522,6 +532,16 @@ dependencies { exclude group: "org.jetbrains.kotlinx", module: "kotlinx-coroutines-debug" } + // Flipper, debug builds only + debugImplementation(libs.flipper.flipper) { + exclude group: 'com.facebook.fbjni', module: 'fbjni' + } + debugImplementation(libs.flipper.flipperNetworkPlugin) { + exclude group: 'com.facebook.fbjni', module: 'fbjni' + } + debugImplementation 'com.facebook.soloader:soloader:0.10.3' + debugImplementation "com.kgurgul.flipper:flipper-realm-android:2.2.0" + // Activate when you want to check for leaks, from time to time. //debugImplementation 'com.squareup.leakcanary:leakcanary-android:2.3' @@ -543,5 +563,7 @@ dependencies { androidTestImplementation('com.adevinta.android:barista:4.2.0') { exclude group: 'org.jetbrains.kotlin' } + androidTestImplementation libs.mockk.mockkAndroid androidTestUtil libs.androidx.orchestrator + debugImplementation libs.androidx.fragmentTesting } diff --git a/vector/sampledata/live_location_users.json b/vector/sampledata/live_location_users.json new file mode 100644 index 0000000000..58d0fb5fa1 --- /dev/null +++ b/vector/sampledata/live_location_users.json @@ -0,0 +1,14 @@ +{ + "data": [ + { + "displayName": "Amandine", + "remainingTime": "9min left", + "lastUpdatedAt": "Updated 12min ago" + }, + { + "displayName": "You", + "remainingTime": "19min left", + "lastUpdatedAt": "Updated 1min ago" + } + ] +} diff --git a/vector/src/androidTest/java/im/vector/app/AndroidVersionTestOverrider.kt b/vector/src/androidTest/java/im/vector/app/AndroidVersionTestOverrider.kt new file mode 100644 index 0000000000..97333b7c98 --- /dev/null +++ b/vector/src/androidTest/java/im/vector/app/AndroidVersionTestOverrider.kt @@ -0,0 +1,46 @@ +/* + * 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 + +import android.os.Build +import java.lang.reflect.Field + +/** + * Used to override [Build.VERSION.SDK_INT]. Ideally an interface should be used instead, but that approach forces us to either add suppress lint annotations + * and potentially miss an API version issue or write a custom lint rule, which seems like an overkill. + */ +object AndroidVersionTestOverrider { + + private var initialValue: Int? = null + + fun override(newVersion: Int) { + if (initialValue == null) { + initialValue = Build.VERSION.SDK_INT + } + val field = Build.VERSION::class.java.getField("SDK_INT") + setStaticField(field, newVersion) + } + + fun restore() { + initialValue?.let { override(it) } + } + + private fun setStaticField(field: Field, value: Any) { + field.isAccessible = true + field.set(null, value) + } +} diff --git a/vector/src/androidTest/java/im/vector/app/EspressoExt.kt b/vector/src/androidTest/java/im/vector/app/EspressoExt.kt index efb28cdff5..72137ed8e8 100644 --- a/vector/src/androidTest/java/im/vector/app/EspressoExt.kt +++ b/vector/src/androidTest/java/im/vector/app/EspressoExt.kt @@ -142,7 +142,7 @@ fun initialSyncIdlingResource(session: Session): IdlingResource { override fun getName() = "InitialSyncIdlingResource for ${session.myUserId}" override fun isIdleNow(): Boolean { - val isIdle = session.hasAlreadySynced() + val isIdle = session.syncService().hasAlreadySynced() return isIdle } @@ -151,16 +151,16 @@ fun initialSyncIdlingResource(session: Session): IdlingResource { } override fun onChanged(t: SyncState?) { - val isIdle = session.hasAlreadySynced() + val isIdle = session.syncService().hasAlreadySynced() if (isIdle) { callback?.onTransitionToIdle() - session.getSyncStateLive().removeObserver(this) + session.syncService().getSyncStateLive().removeObserver(this) } } } runOnUiThread { - session.getSyncStateLive().observeForever(res) + session.syncService().getSyncStateLive().observeForever(res) } return res @@ -202,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 344a2ecfb1..7920e8e0d8 100644 --- a/vector/src/androidTest/java/im/vector/app/RegistrationTest.kt +++ b/vector/src/androidTest/java/im/vector/app/RegistrationTest.kt @@ -29,8 +29,10 @@ import androidx.test.ext.junit.rules.ActivityScenarioRule import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.LargeTest import im.vector.app.features.MainActivity +import im.vector.app.features.analytics.ui.consent.AnalyticsOptInActivity import im.vector.app.features.home.HomeActivity import org.hamcrest.CoreMatchers.not +import org.junit.Ignore import org.junit.Rule import org.junit.Test import org.junit.runner.RunWith @@ -38,6 +40,7 @@ import kotlin.random.Random @RunWith(AndroidJUnit4::class) @LargeTest +@Ignore class RegistrationTest { @get:Rule @@ -104,6 +107,12 @@ class RegistrationTest { .check(matches(isEnabled())) .perform(closeSoftKeyboard(), click()) + withIdlingResource(activityIdlingResource(AnalyticsOptInActivity::class.java)) { + onView(withId(R.id.later)) + .check(matches(isDisplayed())) + .perform(click()) + } + withIdlingResource(activityIdlingResource(HomeActivity::class.java)) { onView(withId(R.id.roomListContainer)) .check(matches(isDisplayed())) diff --git a/vector/src/androidTest/java/im/vector/app/SecurityBootstrapTest.kt b/vector/src/androidTest/java/im/vector/app/SecurityBootstrapTest.kt index 21fd226317..7dc20178f2 100644 --- a/vector/src/androidTest/java/im/vector/app/SecurityBootstrapTest.kt +++ b/vector/src/androidTest/java/im/vector/app/SecurityBootstrapTest.kt @@ -44,6 +44,7 @@ import im.vector.app.features.crypto.recover.SetupMode import im.vector.app.features.home.HomeActivity import org.hamcrest.CoreMatchers.not import org.junit.Before +import org.junit.Ignore import org.junit.Rule import org.junit.Test import org.junit.runner.RunWith @@ -52,6 +53,7 @@ import kotlin.random.Random @RunWith(AndroidJUnit4::class) @LargeTest +@Ignore class SecurityBootstrapTest : VerificationTestBase() { var existingSession: Session? = null @@ -165,7 +167,7 @@ class SecurityBootstrapTest : VerificationTestBase() { assert(uiSession.cryptoService().crossSigningService().isCrossSigningInitialized()) assert(uiSession.cryptoService().crossSigningService().canCrossSign()) assert(uiSession.cryptoService().crossSigningService().allPrivateKeysKnown()) - assert(uiSession.cryptoService().keysBackupService().isEnabled) + assert(uiSession.cryptoService().keysBackupService().isEnabled()) assert(uiSession.cryptoService().keysBackupService().currentBackupVersion != null) assert(uiSession.sharedSecretStorageService().isRecoverySetup()) assert(uiSession.sharedSecretStorageService().isMegolmKeyInBackup()) diff --git a/vector/src/androidTest/java/im/vector/app/TestBuildVersionSdkIntProvider.kt b/vector/src/androidTest/java/im/vector/app/TestBuildVersionSdkIntProvider.kt new file mode 100644 index 0000000000..ddf89b5e46 --- /dev/null +++ b/vector/src/androidTest/java/im/vector/app/TestBuildVersionSdkIntProvider.kt @@ -0,0 +1,25 @@ +/* + * Copyright (c) 2022 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package im.vector.app + +import org.matrix.android.sdk.api.util.BuildVersionSdkIntProvider + +class TestBuildVersionSdkIntProvider : BuildVersionSdkIntProvider { + var value: Int = 0 + + override fun get() = value +} diff --git a/vector/src/androidTest/java/im/vector/app/TestMatrixCallback.kt b/vector/src/androidTest/java/im/vector/app/TestMatrixCallback.kt index 2e254d48ef..f39960ce7e 100644 --- a/vector/src/androidTest/java/im/vector/app/TestMatrixCallback.kt +++ b/vector/src/androidTest/java/im/vector/app/TestMatrixCallback.kt @@ -27,8 +27,10 @@ import java.util.concurrent.CountDownLatch * @param onlySuccessful true to fail if an error occurs. This is the default behavior * @param */ -open class TestMatrixCallback(private val countDownLatch: CountDownLatch, - private val onlySuccessful: Boolean = true) : MatrixCallback { +open class TestMatrixCallback( + private val countDownLatch: CountDownLatch, + private val onlySuccessful: Boolean = true +) : MatrixCallback { @CallSuper override fun onSuccess(data: T) { diff --git a/vector/src/androidTest/java/im/vector/app/VerificationTestBase.kt b/vector/src/androidTest/java/im/vector/app/VerificationTestBase.kt index 6ac3226674..97a2a14da3 100644 --- a/vector/src/androidTest/java/im/vector/app/VerificationTestBase.kt +++ b/vector/src/androidTest/java/im/vector/app/VerificationTestBase.kt @@ -42,10 +42,12 @@ abstract class VerificationTestBase { protected val uiTestBase = OnboardingRobot() - fun createAccountAndSync(matrix: Matrix, - userName: String, - password: String, - withInitialSync: Boolean): Session { + fun createAccountAndSync( + matrix: Matrix, + userName: String, + password: String, + withInitialSync: Boolean + ): Session { val hs = createHomeServerConfig() runBlockingTest { @@ -114,14 +116,14 @@ abstract class VerificationTestBase { GlobalScope.launch(Dispatchers.Main) { session.open() } - session.startSync(true) + session.syncService().startSync(true) val syncLiveData = runBlocking(Dispatchers.Main) { - session.getSyncStateLive() + session.syncService().getSyncStateLive() } val syncObserver = object : Observer { override fun onChanged(t: SyncState?) { - if (session.hasAlreadySynced()) { + if (session.syncService().hasAlreadySynced()) { lock.countDown() syncLiveData.removeObserver(this) } diff --git a/vector/src/androidTest/java/im/vector/app/VerifySessionInteractiveTest.kt b/vector/src/androidTest/java/im/vector/app/VerifySessionInteractiveTest.kt index c970d0049f..8c9faee336 100644 --- a/vector/src/androidTest/java/im/vector/app/VerifySessionInteractiveTest.kt +++ b/vector/src/androidTest/java/im/vector/app/VerifySessionInteractiveTest.kt @@ -38,6 +38,7 @@ import im.vector.app.features.MainActivity import im.vector.app.features.home.HomeActivity import org.hamcrest.CoreMatchers.not import org.junit.Before +import org.junit.Ignore import org.junit.Rule import org.junit.Test import org.junit.runner.RunWith @@ -57,6 +58,7 @@ import kotlin.random.Random @RunWith(AndroidJUnit4::class) @LargeTest +@Ignore class VerifySessionInteractiveTest : VerificationTestBase() { var existingSession: Session? = null diff --git a/vector/src/androidTest/java/im/vector/app/VerifySessionPassphraseTest.kt b/vector/src/androidTest/java/im/vector/app/VerifySessionPassphraseTest.kt index 7efae073e5..23a662dcc8 100644 --- a/vector/src/androidTest/java/im/vector/app/VerifySessionPassphraseTest.kt +++ b/vector/src/androidTest/java/im/vector/app/VerifySessionPassphraseTest.kt @@ -43,6 +43,7 @@ import im.vector.app.features.crypto.recover.SetupMode import im.vector.app.features.home.HomeActivity import kotlinx.coroutines.runBlocking import org.junit.Before +import org.junit.Ignore import org.junit.Rule import org.junit.Test import org.junit.runner.RunWith @@ -57,6 +58,7 @@ import kotlin.random.Random @RunWith(AndroidJUnit4::class) @LargeTest +@Ignore class VerifySessionPassphraseTest : VerificationTestBase() { var existingSession: Session? = null diff --git a/vector/src/androidTest/java/im/vector/app/core/utils/TemporaryStoreTest.kt b/vector/src/androidTest/java/im/vector/app/core/utils/TemporaryStoreTest.kt index d8619f1824..70af6e6419 100644 --- a/vector/src/androidTest/java/im/vector/app/core/utils/TemporaryStoreTest.kt +++ b/vector/src/androidTest/java/im/vector/app/core/utils/TemporaryStoreTest.kt @@ -19,19 +19,20 @@ package im.vector.app.core.utils import org.amshove.kluent.shouldBe import org.junit.Test import java.lang.Thread.sleep +import kotlin.time.Duration.Companion.milliseconds class TemporaryStoreTest { @Test fun testTemporaryStore() { - // Keep the data 30 millis - val store = TemporaryStore(30) + // Keep the data 300 millis + val store = TemporaryStore(300.milliseconds) store.data = "test" store.data shouldBe "test" - sleep(15) + sleep(100) store.data shouldBe "test" - sleep(20) + sleep(300) store.data shouldBe null } } diff --git a/vector/src/androidTest/java/im/vector/app/core/utils/WaitUntil.kt b/vector/src/androidTest/java/im/vector/app/core/utils/WaitUntil.kt new file mode 100644 index 0000000000..16abada04c --- /dev/null +++ b/vector/src/androidTest/java/im/vector/app/core/utils/WaitUntil.kt @@ -0,0 +1,69 @@ +/* + * 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.utils + +import kotlinx.coroutines.delay +import org.amshove.kluent.fail +import kotlin.time.Duration +import kotlin.time.Duration.Companion.milliseconds +import kotlin.time.Duration.Companion.seconds + +/** + * Tries a [condition] several times until it returns true or a [timeout] is reached waiting for some [retryDelay] time between retries. + * On timeout it fails with an [errorMessage]. + */ +suspend fun waitUntilCondition( + errorMessage: String, + timeout: Duration = 1.seconds, + retryDelay: Duration = 50.milliseconds, + condition: () -> Boolean, +) { + val start = System.currentTimeMillis() + do { + if (condition()) return + delay(retryDelay.inWholeMilliseconds) + } while (System.currentTimeMillis() - start < timeout.inWholeMilliseconds) + fail(errorMessage) +} + +/** + * Tries a [block] several times until it runs with no errors or a [timeout] is reached waiting for some [retryDelay] time between retries. + * On timeout it fails with a custom [errorMessage] or a caught [AssertionError]. + */ +suspend fun waitUntil( + errorMessage: String? = null, + timeout: Duration = 1.seconds, + retryDelay: Duration = 50.milliseconds, + block: () -> Unit, +) { + var error: AssertionError? + val start = System.currentTimeMillis() + do { + try { + block() + return + } catch (e: AssertionError) { + error = e + } + delay(retryDelay.inWholeMilliseconds) + } while (System.currentTimeMillis() - start < timeout.inWholeMilliseconds) + if (errorMessage != null) { + fail(errorMessage) + } else { + throw error!! + } +} diff --git a/vector/src/androidTest/java/im/vector/app/features/html/SpanUtilsTest.kt b/vector/src/androidTest/java/im/vector/app/features/html/SpanUtilsTest.kt index 31d8770123..0c8aa95ee4 100644 --- a/vector/src/androidTest/java/im/vector/app/features/html/SpanUtilsTest.kt +++ b/vector/src/androidTest/java/im/vector/app/features/html/SpanUtilsTest.kt @@ -25,10 +25,13 @@ import android.text.style.ForegroundColorSpan import android.text.style.StrikethroughSpan import android.text.style.UnderlineSpan import androidx.emoji2.text.EmojiCompat +import androidx.test.platform.app.InstrumentationRegistry import im.vector.app.InstrumentedTest import org.amshove.kluent.shouldBeEqualTo import org.amshove.kluent.shouldBeTrue +import org.junit.BeforeClass import org.junit.FixMethodOrder +import org.junit.Ignore import org.junit.Test import org.junit.runner.RunWith import org.junit.runners.JUnit4 @@ -38,8 +41,17 @@ import java.util.concurrent.TimeUnit @RunWith(JUnit4::class) @FixMethodOrder(MethodSorters.JVM) +@Ignore class SpanUtilsTest : InstrumentedTest { + companion object { + @BeforeClass + @JvmStatic + fun setupClass() { + EmojiCompat.init(InstrumentationRegistry.getInstrumentation().targetContext) + } + } + private val spanUtils = SpanUtils { val emojiCompat = EmojiCompat.get() emojiCompat.waitForInit() diff --git a/vector/src/androidTest/java/im/vector/app/features/pin/lockscreen/LockScreenTestConstants.kt b/vector/src/androidTest/java/im/vector/app/features/pin/lockscreen/LockScreenTestConstants.kt new file mode 100644 index 0000000000..21e15e1585 --- /dev/null +++ b/vector/src/androidTest/java/im/vector/app/features/pin/lockscreen/LockScreenTestConstants.kt @@ -0,0 +1,21 @@ +/* + * Copyright (c) 2022 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package im.vector.app.features.pin.lockscreen + +object LockScreenTestConstants { + const val ALIAS = "some_alias" +} diff --git a/vector/src/androidTest/java/im/vector/app/features/pin/lockscreen/biometrics/BiometricHelperTests.kt b/vector/src/androidTest/java/im/vector/app/features/pin/lockscreen/biometrics/BiometricHelperTests.kt new file mode 100644 index 0000000000..b519d2f623 --- /dev/null +++ b/vector/src/androidTest/java/im/vector/app/features/pin/lockscreen/biometrics/BiometricHelperTests.kt @@ -0,0 +1,270 @@ +/* + * Copyright (c) 2022 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package im.vector.app.features.pin.lockscreen.biometrics + +import android.content.Intent +import android.os.Build +import androidx.biometric.BiometricManager +import androidx.biometric.BiometricManager.Authenticators.BIOMETRIC_STRONG +import androidx.biometric.BiometricManager.Authenticators.BIOMETRIC_WEAK +import androidx.biometric.BiometricManager.Authenticators.DEVICE_CREDENTIAL +import androidx.biometric.BiometricManager.BIOMETRIC_ERROR_NONE_ENROLLED +import androidx.biometric.BiometricManager.BIOMETRIC_SUCCESS +import androidx.lifecycle.lifecycleScope +import androidx.test.core.app.ActivityScenario +import androidx.test.platform.app.InstrumentationRegistry +import im.vector.app.TestBuildVersionSdkIntProvider +import im.vector.app.features.pin.lockscreen.configuration.LockScreenConfiguration +import im.vector.app.features.pin.lockscreen.configuration.LockScreenConfiguratorProvider +import im.vector.app.features.pin.lockscreen.configuration.LockScreenMode +import im.vector.app.features.pin.lockscreen.crypto.LockScreenKeyRepository +import im.vector.app.features.pin.lockscreen.tests.LockScreenTestActivity +import im.vector.app.features.pin.lockscreen.ui.fallbackprompt.FallbackBiometricDialogFragment +import im.vector.app.features.pin.lockscreen.utils.DevicePromptCheck +import io.mockk.clearAllMocks +import io.mockk.every +import io.mockk.mockk +import io.mockk.mockkObject +import io.mockk.mockkStatic +import io.mockk.spyk +import io.mockk.unmockkObject +import io.mockk.unmockkStatic +import io.mockk.verify +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.channels.Channel +import kotlinx.coroutines.flow.collect +import kotlinx.coroutines.flow.flowOf +import kotlinx.coroutines.flow.receiveAsFlow +import kotlinx.coroutines.launch +import kotlinx.coroutines.test.runTest +import org.amshove.kluent.shouldBeFalse +import org.amshove.kluent.shouldBeTrue +import org.junit.Before +import org.junit.Ignore +import org.junit.Test +import java.util.concurrent.CountDownLatch +import java.util.concurrent.TimeUnit + +class BiometricHelperTests { + + private val biometricManager = mockk(relaxed = true) + private val lockScreenKeyRepository = mockk(relaxed = true) + private val buildVersionSdkIntProvider = TestBuildVersionSdkIntProvider() + + @Before + fun setup() { + clearAllMocks() + } + + @Test + fun canUseWeakBiometricAuthReturnsTrueIfIsFaceUnlockEnabledAndCanAuthenticate() { + every { biometricManager.canAuthenticate(BIOMETRIC_WEAK) } returns BIOMETRIC_SUCCESS + val configuration = createDefaultConfiguration(isFaceUnlockEnabled = true) + val biometricUtils = createBiometricHelper(configuration) + + biometricUtils.canUseWeakBiometricAuth.shouldBeTrue() + + val biometricUtilsWithDisabledAuth = createBiometricHelper(createDefaultConfiguration(isFaceUnlockEnabled = false)) + biometricUtilsWithDisabledAuth.canUseWeakBiometricAuth.shouldBeFalse() + + every { biometricManager.canAuthenticate(BIOMETRIC_WEAK) } returns BIOMETRIC_ERROR_NONE_ENROLLED + biometricUtils.canUseWeakBiometricAuth.shouldBeFalse() + } + + @Test + fun canUseStrongBiometricAuthReturnsTrueIfIsBiometricsEnabledAndCanAuthenticate() { + every { biometricManager.canAuthenticate(BIOMETRIC_STRONG) } returns BIOMETRIC_SUCCESS + val configuration = createDefaultConfiguration(isBiometricsEnabled = true) + val biometricUtils = createBiometricHelper(configuration) + + biometricUtils.canUseStrongBiometricAuth.shouldBeTrue() + + val biometricUtilsWithDisabledAuth = createBiometricHelper(createDefaultConfiguration(isBiometricsEnabled = false)) + biometricUtilsWithDisabledAuth.canUseStrongBiometricAuth.shouldBeFalse() + + every { biometricManager.canAuthenticate(BIOMETRIC_STRONG) } returns BIOMETRIC_ERROR_NONE_ENROLLED + biometricUtils.canUseStrongBiometricAuth.shouldBeFalse() + } + + @Test + fun canUseDeviceCredentialAuthReturnsTrueIfIsDeviceCredentialsUnlockEnabledAndCanAuthenticate() { + every { biometricManager.canAuthenticate(DEVICE_CREDENTIAL) } returns BIOMETRIC_SUCCESS + val configuration = createDefaultConfiguration(isDeviceCredentialUnlockEnabled = true) + val biometricUtils = createBiometricHelper(configuration) + + biometricUtils.canUseDeviceCredentialsAuth.shouldBeTrue() + + val biometricUtilsWithDisabledAuth = createBiometricHelper(createDefaultConfiguration(isDeviceCredentialUnlockEnabled = false)) + biometricUtilsWithDisabledAuth.canUseDeviceCredentialsAuth.shouldBeFalse() + + every { biometricManager.canAuthenticate(DEVICE_CREDENTIAL) } returns BIOMETRIC_ERROR_NONE_ENROLLED + biometricUtils.canUseDeviceCredentialsAuth.shouldBeFalse() + } + + @Test + fun isSystemAuthEnabledReturnsTrueIfAnyAuthenticationMethodIsAvailableAndEnabledAndSystemKeyExists() { + val biometricHelper = mockk(relaxed = true) { + every { hasSystemKey } returns true + every { isSystemKeyValid } returns true + every { canUseAnySystemAuth } answers { callOriginal() } + every { isSystemAuthEnabledAndValid } answers { callOriginal() } + } + biometricHelper.isSystemAuthEnabledAndValid.shouldBeFalse() + + every { biometricHelper.canUseWeakBiometricAuth } returns true + biometricHelper.isSystemAuthEnabledAndValid.shouldBeTrue() + + every { biometricHelper.canUseWeakBiometricAuth } returns false + every { biometricHelper.canUseStrongBiometricAuth } returns true + biometricHelper.isSystemAuthEnabledAndValid.shouldBeTrue() + + every { biometricHelper.canUseStrongBiometricAuth } returns false + every { biometricHelper.canUseDeviceCredentialsAuth } returns true + biometricHelper.isSystemAuthEnabledAndValid.shouldBeTrue() + + every { biometricHelper.isSystemKeyValid } returns false + biometricHelper.isSystemAuthEnabledAndValid.shouldBeFalse() + } + + @Test + fun hasSystemKeyReturnsKeyHelperHasSystemKey() { + val biometricUtils = createBiometricHelper(createDefaultConfiguration()) + every { lockScreenKeyRepository.hasSystemKey() } returns true + biometricUtils.hasSystemKey.shouldBeTrue() + + every { lockScreenKeyRepository.hasSystemKey() } returns false + biometricUtils.hasSystemKey.shouldBeFalse() + } + + @Test + fun isSystemKeyValidReturnsKeyHelperIsSystemKeyValid() { + val biometricUtils = createBiometricHelper(createDefaultConfiguration()) + every { lockScreenKeyRepository.isSystemKeyValid() } returns true + biometricUtils.isSystemKeyValid.shouldBeTrue() + + every { lockScreenKeyRepository.isSystemKeyValid() } returns false + biometricUtils.isSystemKeyValid.shouldBeFalse() + } + + @Test + fun disableAuthenticationDeletesSystemKeyAndCancelsPrompt() { + val biometricUtils = spyk(createBiometricHelper(createDefaultConfiguration())) + biometricUtils.disableAuthentication() + + verify { lockScreenKeyRepository.deleteSystemKey() } + verify { biometricUtils.cancelPrompt() } + } + + @OptIn(ExperimentalCoroutinesApi::class) + @Ignore("This won't work in CI as the emulator won't have biometric auth enabled.") + @Test + fun authenticateShowsPrompt() = runTest { + val biometricUtils = createBiometricHelper(createDefaultConfiguration(isBiometricsEnabled = true)) + every { lockScreenKeyRepository.isSystemKeyValid() } returns true + val latch = CountDownLatch(1) + with(ActivityScenario.launch(LockScreenTestActivity::class.java)) { + onActivity { activity -> + biometricUtils.authenticate(activity) + activity.supportFragmentManager.fragments.isNotEmpty().shouldBeTrue() + close() + latch.countDown() + } + } + latch.await(1, TimeUnit.SECONDS) + } + + @OptIn(ExperimentalCoroutinesApi::class) + @Test + fun authenticateInDeviceWithIssuesShowsFallbackPromptDialog() = runTest { + mockkStatic("kotlinx.coroutines.flow.FlowKt") + val mockAuthChannel: Channel = mockk(relaxed = true) { + // Empty flow to keep the dialog open + every { receiveAsFlow() } returns flowOf() + } + val biometricUtils = spyk(createBiometricHelper(createDefaultConfiguration(isBiometricsEnabled = true))) { + every { createAuthChannel() } returns mockAuthChannel + } + mockkObject(DevicePromptCheck) + every { DevicePromptCheck.isDeviceWithNoBiometricUI } returns true + every { lockScreenKeyRepository.isSystemKeyValid() } returns true + val latch = CountDownLatch(1) + val intent = Intent(InstrumentationRegistry.getInstrumentation().targetContext, LockScreenTestActivity::class.java) + with(ActivityScenario.launch(intent)) { + onActivity { activity -> + biometricUtils.authenticate(activity) + launch { + activity.supportFragmentManager.fragments.any { it is FallbackBiometricDialogFragment }.shouldBeTrue() + close() + latch.countDown() + } + } + } + latch.await(1, TimeUnit.SECONDS) + unmockkObject(DevicePromptCheck) + unmockkStatic("kotlinx.coroutines.flow.FlowKt") + } + + @Test + fun authenticateCreatesSystemKeyIfNeededOnSuccessOnAndroidM() = runTest { + buildVersionSdkIntProvider.value = Build.VERSION_CODES.M + every { lockScreenKeyRepository.isSystemKeyValid() } returns true + val mockAuthChannel = Channel(capacity = 1) + val biometricUtils = spyk(createBiometricHelper(createDefaultConfiguration(isBiometricsEnabled = true))) { + every { createAuthChannel() } returns mockAuthChannel + every { authenticateWithPromptInternal(any(), any(), any()) } returns mockk() + } + + val latch = CountDownLatch(1) + val intent = Intent(InstrumentationRegistry.getInstrumentation().targetContext, LockScreenTestActivity::class.java) + ActivityScenario.launch(intent).onActivity { activity -> + activity.lifecycleScope.launch { + launch { + mockAuthChannel.send(true) + mockAuthChannel.close() + } + biometricUtils.authenticate(activity).collect() + latch.countDown() + } + } + + latch.await(1, TimeUnit.SECONDS) + verify { lockScreenKeyRepository.ensureSystemKey() } + } + + private fun createBiometricHelper(configuration: LockScreenConfiguration): BiometricHelper { + val context = InstrumentationRegistry.getInstrumentation().targetContext + val configProvider = LockScreenConfiguratorProvider(configuration) + return BiometricHelper(context, lockScreenKeyRepository, configProvider, biometricManager, buildVersionSdkIntProvider) + } + + private fun createDefaultConfiguration( + mode: LockScreenMode = LockScreenMode.VERIFY, + pinCodeLength: Int = 4, + isBiometricsEnabled: Boolean = false, + isFaceUnlockEnabled: Boolean = false, + isDeviceCredentialUnlockEnabled: Boolean = false, + needsNewCodeValidation: Boolean = false, + otherChanges: LockScreenConfiguration.() -> LockScreenConfiguration = { this }, + ): LockScreenConfiguration = LockScreenConfiguration( + mode, + pinCodeLength, + isBiometricsEnabled, + isFaceUnlockEnabled, + isDeviceCredentialUnlockEnabled, + needsNewCodeValidation + ).let(otherChanges) +} diff --git a/vector/src/androidTest/java/im/vector/app/features/pin/lockscreen/crypto/KeyStoreCryptoTests.kt b/vector/src/androidTest/java/im/vector/app/features/pin/lockscreen/crypto/KeyStoreCryptoTests.kt new file mode 100644 index 0000000000..68e1244791 --- /dev/null +++ b/vector/src/androidTest/java/im/vector/app/features/pin/lockscreen/crypto/KeyStoreCryptoTests.kt @@ -0,0 +1,155 @@ +/* + * Copyright (c) 2022 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package im.vector.app.features.pin.lockscreen.crypto + +import android.os.Build +import android.security.keystore.KeyPermanentlyInvalidatedException +import androidx.test.platform.app.InstrumentationRegistry +import im.vector.app.TestBuildVersionSdkIntProvider +import io.mockk.every +import io.mockk.spyk +import io.mockk.verify +import org.amshove.kluent.invoking +import org.amshove.kluent.shouldBe +import org.amshove.kluent.shouldBeEqualTo +import org.amshove.kluent.shouldBeFalse +import org.amshove.kluent.shouldBeTrue +import org.amshove.kluent.shouldThrow +import org.junit.After +import org.junit.Test +import org.matrix.android.sdk.api.securestorage.SecretStoringUtils +import java.security.KeyStore + +class KeyStoreCryptoTests { + + private val alias = "some_alias" + + private val context = InstrumentationRegistry.getInstrumentation().targetContext + private val keyStore = KeyStore.getInstance("AndroidKeyStore").also { it.load(null) } + private val versionProvider = TestBuildVersionSdkIntProvider().also { it.value = Build.VERSION_CODES.M } + private val secretStoringUtils = spyk(SecretStoringUtils(context, keyStore, versionProvider)) + private val keyStoreCrypto = spyk( + KeyStoreCrypto(alias, false, context, versionProvider, keyStore, secretStoringUtils) + ) + + @After + fun setup() { + keyStore.deleteEntry(alias) + } + + @Test + fun ensureKeyChecksValidityOfKeyAndThrows() { + keyStore.containsAlias(alias) shouldBe false + + val exception = KeyPermanentlyInvalidatedException() + every { secretStoringUtils.getEncryptCipher(any()) } throws exception + + invoking { keyStoreCrypto.ensureKey() } shouldThrow exception + keyStoreCrypto.hasValidKey() shouldBe false + } + + @Test + fun hasValidKeyChecksValidityOfKey() { + runCatching { keyStoreCrypto.ensureKey() } + keyStoreCrypto.hasValidKey() shouldBe true + + val exception = KeyPermanentlyInvalidatedException() + every { secretStoringUtils.getEncryptCipher(any()) } throws exception + + runCatching { keyStoreCrypto.ensureKey() } + keyStoreCrypto.hasValidKey() shouldBe false + } + + @Test + fun hasKeyChecksIfKeyExists() { + keyStoreCrypto.hasKey() shouldBe false + + keyStoreCrypto.ensureKey() + keyStoreCrypto.hasKey() shouldBe true + keyStore.containsAlias(keyStoreCrypto.alias) + + keyStoreCrypto.deleteKey() + keyStoreCrypto.hasKey() shouldBe false + } + + @Test + fun deleteKeyRemovesTheKey() { + keyStore.containsAlias(alias) shouldBe false + + keyStoreCrypto.ensureKey() + keyStore.containsAlias(alias) shouldBe true + + keyStoreCrypto.deleteKey() + keyStore.containsAlias(alias) shouldBe false + } + + @Test + fun checkEncryptionAndDecryptionOfStringsWorkAsExpected() { + val original = "some plain text" + val encryptedString = keyStoreCrypto.encryptToString(original) + val encryptedBytes = keyStoreCrypto.encrypt(original) + val result = keyStoreCrypto.decryptToString(encryptedString) + val resultFromBytes = keyStoreCrypto.decryptToString(encryptedBytes) + result shouldBeEqualTo original + resultFromBytes shouldBeEqualTo original + } + + @Test + fun checkEncryptionAndDecryptionWorkAsExpected() { + val original = "some plain text".toByteArray() + val encryptedBytes = keyStoreCrypto.encrypt(original) + val encryptedString = keyStoreCrypto.encryptToString(original) + val result = keyStoreCrypto.decrypt(encryptedBytes) + val resultFromString = keyStoreCrypto.decrypt(encryptedString) + result shouldBeEqualTo original + resultFromString shouldBeEqualTo original + } + + @Test + fun hasValidKeyReturnsFalseWhenKeyPermanentlyInvalidatedExceptionIsThrown() { + every { keyStoreCrypto.hasKey() } returns true + every { secretStoringUtils.getEncryptCipher(any()) } throws KeyPermanentlyInvalidatedException() + + keyStoreCrypto.hasValidKey().shouldBeFalse() + } + + @Test + fun hasValidKeyReturnsFalseWhenKeyDoesNotExist() { + every { keyStoreCrypto.hasKey() } returns false + keyStoreCrypto.hasValidKey().shouldBeFalse() + } + + @Test + fun hasValidKeyReturnsIfKeyExistsOnAndroidL() { + versionProvider.value = Build.VERSION_CODES.LOLLIPOP + + every { keyStoreCrypto.hasKey() } returns true + keyStoreCrypto.hasValidKey().shouldBeTrue() + + every { keyStoreCrypto.hasKey() } returns false + keyStoreCrypto.hasValidKey().shouldBeFalse() + } + + @Test + fun getCryptoObjectUsesCipherFromSecretStoringUtils() { + keyStoreCrypto.getCryptoObject() + verify { secretStoringUtils.getEncryptCipher(any()) } + + every { secretStoringUtils.getEncryptCipher(any()) } throws KeyPermanentlyInvalidatedException() + invoking { keyStoreCrypto.getCryptoObject() } shouldThrow KeyPermanentlyInvalidatedException::class + } +} diff --git a/vector/src/androidTest/java/im/vector/app/features/pin/lockscreen/crypto/LockScreenKeyRepositoryTests.kt b/vector/src/androidTest/java/im/vector/app/features/pin/lockscreen/crypto/LockScreenKeyRepositoryTests.kt new file mode 100644 index 0000000000..23eefe6577 --- /dev/null +++ b/vector/src/androidTest/java/im/vector/app/features/pin/lockscreen/crypto/LockScreenKeyRepositoryTests.kt @@ -0,0 +1,184 @@ +/* + * Copyright (c) 2022 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package im.vector.app.features.pin.lockscreen.crypto + +import android.security.keystore.KeyPermanentlyInvalidatedException +import androidx.test.platform.app.InstrumentationRegistry +import im.vector.app.features.settings.VectorPreferences +import io.mockk.clearAllMocks +import io.mockk.coVerify +import io.mockk.every +import io.mockk.mockk +import io.mockk.spyk +import io.mockk.verify +import kotlinx.coroutines.test.runTest +import org.amshove.kluent.coInvoking +import org.amshove.kluent.shouldBeEqualTo +import org.amshove.kluent.shouldBeFalse +import org.amshove.kluent.shouldBeTrue +import org.amshove.kluent.shouldNotThrow +import org.junit.After +import org.junit.Before +import org.junit.Test +import org.matrix.android.sdk.api.util.DefaultBuildVersionSdkIntProvider +import java.security.KeyStore + +class LockScreenKeyRepositoryTests { + + private val context = InstrumentationRegistry.getInstrumentation().targetContext + private val buildVersionSdkIntProvider = DefaultBuildVersionSdkIntProvider() + + private val keyStoreCryptoFactory: KeyStoreCrypto.Factory = mockk { + every { provide(any(), any()) } answers { + KeyStoreCrypto(arg(0), false, context, buildVersionSdkIntProvider, keyStore) + } + } + + private lateinit var lockScreenKeyRepository: LockScreenKeyRepository + private val pinCodeMigrator: PinCodeMigrator = mockk(relaxed = true) + private val vectorPreferences: VectorPreferences = mockk(relaxed = true) + + private val keyStore: KeyStore by lazy { + KeyStore.getInstance(LockScreenCryptoConstants.ANDROID_KEY_STORE).also { it.load(null) } + } + + @Before + fun setup() { + lockScreenKeyRepository = spyk(LockScreenKeyRepository("base", pinCodeMigrator, vectorPreferences, keyStoreCryptoFactory)) + } + + @After + fun tearDown() { + clearAllMocks() + keyStore.deleteEntry("base.pin_code") + keyStore.deleteEntry("base.system") + } + + @Test + fun ensureSystemKeyCreatesSystemKeyIfNeeded() { + lockScreenKeyRepository.ensureSystemKey() + lockScreenKeyRepository.hasSystemKey().shouldBeTrue() + } + + @Test + fun encryptPinCodeCreatesPinCodeKey() { + lockScreenKeyRepository.encryptPinCode("1234") + lockScreenKeyRepository.hasPinCodeKey().shouldBeTrue() + } + + @Test + fun decryptPinCodeDecryptsEncodedPinCode() { + val decodedPinCode = "1234" + val pinCodeKeyCryptoMock = mockk(relaxed = true) { + every { decryptToString(any()) } returns decodedPinCode + } + every { keyStoreCryptoFactory.provide(any(), any()) } returns pinCodeKeyCryptoMock + lockScreenKeyRepository.decryptPinCode("SOME_VALUE") shouldBeEqualTo decodedPinCode + } + + @Test + fun isSystemKeyValidReturnsWhatKeyStoreCryptoHasValidKeyReplies() { + val systemKeyCryptoMock = mockk(relaxed = true) { + every { hasKey() } returns true + } + every { keyStoreCryptoFactory.provide(any(), any()) } returns systemKeyCryptoMock + + every { systemKeyCryptoMock.hasValidKey() } returns false + lockScreenKeyRepository.isSystemKeyValid().shouldBeFalse() + + every { systemKeyCryptoMock.hasValidKey() } returns true + lockScreenKeyRepository.isSystemKeyValid().shouldBeTrue() + } + + @Test + fun hasSystemKeyReturnsTrueAfterSystemKeyIsCreated() { + lockScreenKeyRepository.hasSystemKey().shouldBeFalse() + + lockScreenKeyRepository.ensureSystemKey() + + lockScreenKeyRepository.hasSystemKey().shouldBeTrue() + } + + @Test + fun hasPinCodeKeyReturnsTrueAfterPinCodeKeyIsCreated() { + lockScreenKeyRepository.hasPinCodeKey().shouldBeFalse() + + lockScreenKeyRepository.encryptPinCode("1234") + + lockScreenKeyRepository.hasPinCodeKey().shouldBeTrue() + } + + @Test + fun deleteSystemKeyRemovesTheKeyFromKeyStore() { + lockScreenKeyRepository.ensureSystemKey() + lockScreenKeyRepository.hasSystemKey().shouldBeTrue() + + lockScreenKeyRepository.deleteSystemKey() + + lockScreenKeyRepository.hasSystemKey().shouldBeFalse() + } + + @Test + fun deletePinCodeKeyRemovesTheKeyFromKeyStore() { + lockScreenKeyRepository.encryptPinCode("1234") + lockScreenKeyRepository.hasPinCodeKey().shouldBeTrue() + + lockScreenKeyRepository.deletePinCodeKey() + + lockScreenKeyRepository.hasPinCodeKey().shouldBeFalse() + } + + @Test + fun migrateKeysIfNeededReturnsEarlyIfNotNeeded() = runTest { + every { pinCodeMigrator.isMigrationNeeded() } returns false + + lockScreenKeyRepository.migrateKeysIfNeeded() + + coVerify(exactly = 0) { pinCodeMigrator.migrate(any()) } + } + + @Test + fun migrateKeysIfNeededWillMigratePinCodeAndKeys() = runTest { + every { pinCodeMigrator.isMigrationNeeded() } returns true + + lockScreenKeyRepository.migrateKeysIfNeeded() + + coVerify { pinCodeMigrator.migrate(any()) } + } + + @Test + fun migrateKeysIfNeededWillCreateSystemKeyIfNeeded() = runTest { + every { pinCodeMigrator.isMigrationNeeded() } returns true + every { vectorPreferences.useBiometricsToUnlock() } returns true + every { lockScreenKeyRepository.ensureSystemKey() } returns mockk() + + lockScreenKeyRepository.migrateKeysIfNeeded() + + verify { lockScreenKeyRepository.ensureSystemKey() } + } + + @Test + fun migrateKeysIfNeededWillHandleKeyPermanentlyInvalidatedException() = runTest { + every { pinCodeMigrator.isMigrationNeeded() } returns true + every { vectorPreferences.useBiometricsToUnlock() } returns true + every { lockScreenKeyRepository.ensureSystemKey() } throws KeyPermanentlyInvalidatedException() + + coInvoking { lockScreenKeyRepository.migrateKeysIfNeeded() } shouldNotThrow KeyPermanentlyInvalidatedException::class + + verify { lockScreenKeyRepository.ensureSystemKey() } + } +} diff --git a/vector/src/androidTest/java/im/vector/app/features/pin/lockscreen/crypto/PinCodeMigratorTests.kt b/vector/src/androidTest/java/im/vector/app/features/pin/lockscreen/crypto/PinCodeMigratorTests.kt new file mode 100644 index 0000000000..297793c7a4 --- /dev/null +++ b/vector/src/androidTest/java/im/vector/app/features/pin/lockscreen/crypto/PinCodeMigratorTests.kt @@ -0,0 +1,236 @@ +/* + * 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. + */ + +@file:Suppress("DEPRECATION") + +package im.vector.app.features.pin.lockscreen.crypto + +import android.os.Build +import android.security.KeyPairGeneratorSpec +import android.security.keystore.KeyGenParameterSpec +import android.security.keystore.KeyProperties +import android.util.Base64 +import androidx.preference.PreferenceManager +import androidx.test.platform.app.InstrumentationRegistry +import im.vector.app.features.pin.PinCodeStore +import im.vector.app.features.pin.SharedPrefPinCodeStore +import im.vector.app.features.pin.lockscreen.crypto.LockScreenCryptoConstants.ANDROID_KEY_STORE +import im.vector.app.features.pin.lockscreen.crypto.LockScreenCryptoConstants.LEGACY_PIN_CODE_KEY_ALIAS +import io.mockk.coEvery +import io.mockk.coVerify +import io.mockk.every +import io.mockk.mockk +import io.mockk.spyk +import io.mockk.verify +import kotlinx.coroutines.runBlocking +import kotlinx.coroutines.test.runTest +import org.amshove.kluent.shouldBe +import org.amshove.kluent.shouldBeEqualTo +import org.junit.After +import org.junit.Test +import org.matrix.android.sdk.api.securestorage.SecretStoringUtils +import org.matrix.android.sdk.api.util.BuildVersionSdkIntProvider +import java.math.BigInteger +import java.security.KeyFactory +import java.security.KeyPairGenerator +import java.security.KeyStore +import java.security.spec.MGF1ParameterSpec +import java.security.spec.X509EncodedKeySpec +import java.util.Calendar +import java.util.UUID +import javax.crypto.Cipher +import javax.crypto.spec.OAEPParameterSpec +import javax.crypto.spec.PSource +import javax.security.auth.x500.X500Principal +import kotlin.math.abs + +class PinCodeMigratorTests { + + private val alias = UUID.randomUUID().toString() + + private val context = InstrumentationRegistry.getInstrumentation().targetContext + private val pinCodeStore: PinCodeStore = spyk( + SharedPrefPinCodeStore(PreferenceManager.getDefaultSharedPreferences(InstrumentationRegistry.getInstrumentation().context)) + ) + private val keyStore: KeyStore = spyk(KeyStore.getInstance(ANDROID_KEY_STORE)).also { it.load(null) } + private val buildVersionSdkIntProvider: BuildVersionSdkIntProvider = mockk { + every { get() } returns Build.VERSION_CODES.M + } + private val secretStoringUtils: SecretStoringUtils = spyk( + SecretStoringUtils(context, keyStore, buildVersionSdkIntProvider) + ) + private val pinCodeMigrator = spyk(PinCodeMigrator(pinCodeStore, keyStore, secretStoringUtils, buildVersionSdkIntProvider)) + + @After + fun tearDown() { + if (keyStore.containsAlias(LEGACY_PIN_CODE_KEY_ALIAS)) { + keyStore.deleteEntry(LEGACY_PIN_CODE_KEY_ALIAS) + } + if (keyStore.containsAlias(alias)) { + keyStore.deleteEntry(alias) + } + runBlocking { pinCodeStore.deletePinCode() } + } + + @Test + fun isMigrationNeededReturnsTrueIfLegacyKeyExists() { + pinCodeMigrator.isMigrationNeeded() shouldBe false + + generateLegacyKey() + + pinCodeMigrator.isMigrationNeeded() shouldBe true + } + + @Test + fun migrateWillReturnEarlyIfPinCodeDoesNotExist() = runTest { + every { pinCodeMigrator.isMigrationNeeded() } returns false + coEvery { pinCodeStore.getPinCode() } returns null + + pinCodeMigrator.migrate(alias) + + coVerify(exactly = 0) { pinCodeMigrator.getDecryptedPinCode() } + verify(exactly = 0) { secretStoringUtils.securelyStoreBytes(any(), any()) } + coVerify(exactly = 0) { pinCodeStore.savePinCode(any()) } + verify(exactly = 0) { keyStore.deleteEntry(LEGACY_PIN_CODE_KEY_ALIAS) } + } + + @Test + fun migrateWillReturnEarlyIfIsNotNeeded() = runTest { + every { pinCodeMigrator.isMigrationNeeded() } returns false + coEvery { pinCodeMigrator.getDecryptedPinCode() } returns "1234" + every { secretStoringUtils.securelyStoreBytes(any(), any()) } returns ByteArray(0) + + pinCodeMigrator.migrate(alias) + + coVerify(exactly = 0) { pinCodeMigrator.getDecryptedPinCode() } + verify(exactly = 0) { secretStoringUtils.securelyStoreBytes(any(), any()) } + coVerify(exactly = 0) { pinCodeStore.savePinCode(any()) } + verify(exactly = 0) { keyStore.deleteEntry(LEGACY_PIN_CODE_KEY_ALIAS) } + } + + @Test + fun migratePinCodeM() = runTest { + val pinCode = "1234" + saveLegacyPinCode(pinCode) + + pinCodeMigrator.migrate(alias) + + coVerify { pinCodeMigrator.getDecryptedPinCode() } + verify { secretStoringUtils.securelyStoreBytes(any(), any()) } + coVerify { pinCodeStore.savePinCode(any()) } + verify { keyStore.deleteEntry(LEGACY_PIN_CODE_KEY_ALIAS) } + + val decodedPinCode = String(secretStoringUtils.loadSecureSecretBytes(Base64.decode(pinCodeStore.getPinCode().orEmpty(), Base64.NO_WRAP), alias)) + decodedPinCode shouldBeEqualTo pinCode + keyStore.containsAlias(LEGACY_PIN_CODE_KEY_ALIAS) shouldBe false + keyStore.containsAlias(alias) shouldBe true + } + + @Test + fun migratePinCodeL() = runTest { + val pinCode = "1234" + every { buildVersionSdkIntProvider.get() } returns Build.VERSION_CODES.LOLLIPOP + saveLegacyPinCode(pinCode) + + pinCodeMigrator.migrate(alias) + + coVerify { pinCodeMigrator.getDecryptedPinCode() } + verify { secretStoringUtils.securelyStoreBytes(any(), any()) } + coVerify { pinCodeStore.savePinCode(any()) } + verify { keyStore.deleteEntry(LEGACY_PIN_CODE_KEY_ALIAS) } + + val decodedPinCode = String(secretStoringUtils.loadSecureSecretBytes(Base64.decode(pinCodeStore.getPinCode().orEmpty(), Base64.NO_WRAP), alias)) + decodedPinCode shouldBeEqualTo pinCode + keyStore.containsAlias(LEGACY_PIN_CODE_KEY_ALIAS) shouldBe false + keyStore.containsAlias(alias) shouldBe true + } + + private fun generateLegacyKey() { + if (keyStore.containsAlias(LEGACY_PIN_CODE_KEY_ALIAS)) return + + if (buildVersionSdkIntProvider.get() >= Build.VERSION_CODES.M) { + generateLegacyKeyM() + } else { + generateLegacyKeyL() + } + } + + private fun generateLegacyKeyL() { + val start = Calendar.getInstance() + val end = Calendar.getInstance().also { it.add(Calendar.YEAR, 25) } + + val keyGen = KeyPairGenerator + .getInstance(KeyProperties.KEY_ALGORITHM_RSA, ANDROID_KEY_STORE) + + val spec = KeyPairGeneratorSpec.Builder(context) + .setAlias(LEGACY_PIN_CODE_KEY_ALIAS) + .setSubject(X500Principal("CN=$LEGACY_PIN_CODE_KEY_ALIAS")) + .setSerialNumber(BigInteger.valueOf(abs(LEGACY_PIN_CODE_KEY_ALIAS.hashCode()).toLong())) + .setEndDate(end.time) + .setStartDate(start.time) + .setSerialNumber(BigInteger.ONE) + .setSubject(X500Principal("CN = Secured Preference Store, O = Devliving Online")) + .build() + + keyGen.initialize(spec) + keyGen.generateKeyPair() + } + + private fun generateLegacyKeyM() { + val keyGenerator: KeyPairGenerator = KeyPairGenerator.getInstance(KeyProperties.KEY_ALGORITHM_RSA, ANDROID_KEY_STORE) + keyGenerator.initialize( + KeyGenParameterSpec.Builder(LEGACY_PIN_CODE_KEY_ALIAS, KeyProperties.PURPOSE_ENCRYPT or KeyProperties.PURPOSE_DECRYPT) + .setDigests(KeyProperties.DIGEST_SHA256, KeyProperties.DIGEST_SHA512) + .setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_RSA_OAEP) + .build() + ) + keyGenerator.generateKeyPair() + } + + private suspend fun saveLegacyPinCode(value: String) { + generateLegacyKey() + val publicKey = keyStore.getCertificate(LEGACY_PIN_CODE_KEY_ALIAS).publicKey + val cipher = getLegacyCipher() + if (buildVersionSdkIntProvider.get() >= Build.VERSION_CODES.M) { + val unrestrictedKey = KeyFactory.getInstance(publicKey.algorithm).generatePublic(X509EncodedKeySpec(publicKey.encoded)) + val spec = OAEPParameterSpec("SHA-256", "MGF1", MGF1ParameterSpec.SHA1, PSource.PSpecified.DEFAULT) + cipher.init(Cipher.ENCRYPT_MODE, unrestrictedKey, spec) + } else { + cipher.init(Cipher.ENCRYPT_MODE, publicKey) + } + val bytes = cipher.doFinal(value.toByteArray()) + val encryptedPinCode = Base64.encodeToString(bytes, Base64.NO_WRAP) + pinCodeStore.savePinCode(encryptedPinCode) + } + + private fun getLegacyCipher(): Cipher { + return when (buildVersionSdkIntProvider.get()) { + Build.VERSION_CODES.LOLLIPOP, Build.VERSION_CODES.LOLLIPOP_MR1 -> getCipherL() + else -> getCipherM() + } + } + + private fun getCipherL(): Cipher { + val provider = if (buildVersionSdkIntProvider.get() < Build.VERSION_CODES.M) "AndroidOpenSSL" else "AndroidKeyStoreBCWorkaround" + val transformation = "RSA/ECB/PKCS1Padding" + return Cipher.getInstance(transformation, provider) + } + + private fun getCipherM(): Cipher { + val transformation = "RSA/ECB/OAEPWithSHA-256AndMGF1Padding" + return Cipher.getInstance(transformation) + } +} diff --git a/vector/src/androidTest/java/im/vector/app/features/pin/lockscreen/tests/LockScreenTestActivity.kt b/vector/src/androidTest/java/im/vector/app/features/pin/lockscreen/tests/LockScreenTestActivity.kt new file mode 100644 index 0000000000..1545e140a0 --- /dev/null +++ b/vector/src/androidTest/java/im/vector/app/features/pin/lockscreen/tests/LockScreenTestActivity.kt @@ -0,0 +1,21 @@ +/* + * Copyright (c) 2022 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package im.vector.app.features.pin.lockscreen.tests + +import androidx.fragment.app.FragmentActivity + +class LockScreenTestActivity : FragmentActivity() diff --git a/vector/src/androidTest/java/im/vector/app/features/pin/lockscreen/ui/fallbackprompt/FallbackBiometricDialogFragmentTests.kt b/vector/src/androidTest/java/im/vector/app/features/pin/lockscreen/ui/fallbackprompt/FallbackBiometricDialogFragmentTests.kt new file mode 100644 index 0000000000..3781535f72 --- /dev/null +++ b/vector/src/androidTest/java/im/vector/app/features/pin/lockscreen/ui/fallbackprompt/FallbackBiometricDialogFragmentTests.kt @@ -0,0 +1,122 @@ +/* + * Copyright (c) 2022 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package im.vector.app.features.pin.lockscreen.ui.fallbackprompt + +import android.view.View +import android.widget.Button +import android.widget.TextView +import androidx.core.os.bundleOf +import androidx.fragment.app.DialogFragment +import androidx.fragment.app.testing.launchFragment +import androidx.lifecycle.Lifecycle +import androidx.test.platform.app.InstrumentationRegistry +import com.airbnb.mvrx.Mavericks +import im.vector.app.R +import kotlinx.coroutines.channels.Channel +import kotlinx.coroutines.flow.MutableSharedFlow +import kotlinx.coroutines.flow.receiveAsFlow +import org.amshove.kluent.shouldBeEqualTo +import org.junit.Test +import java.util.concurrent.CountDownLatch + +class FallbackBiometricDialogFragmentTests { + + private val context = InstrumentationRegistry.getInstrumentation().targetContext + + @Test + fun dismissTriggersOnDismissCallback() { + val latch = CountDownLatch(1) + val fragmentScenario = launchFragment(noArgsBundle()) + fragmentScenario.onFragment { fragment -> + fragment.onDismiss = { latch.countDown() } + fragment.dismiss() + } + latch.await() + } + + @Test + fun argsModifyUI() { + val latch = CountDownLatch(1) + val args = FallbackBiometricDialogFragment.Args( + title = "Title", + description = "Description", + cancelActionText = "Cancel text", + ) + val fragmentScenario = launchFragment(bundleOf(Mavericks.KEY_ARG to args)) + fragmentScenario.onFragment { fragment -> + val view = fragment.requireView() + view.findViewById